
Few Simple Experimentations
Testing a few simple experimentations and visualizations
Prompt
Create a maze generator and pathfinding visualizer. Randomly generate two mazes and visualize A* and Djikstra's algorithm solving it step by step next to each other. Show the time that it took each for each to solve it at the bottom of the maze. Use canvas and animations. Make it visually appealing. Contain the maze to be 10 x 10 max
A system prompt was added to support web rendering
# Maze Generator and Pathfinding Visualizer I'll create a visually appealing maze generator with A* and Dijkstra's algorithm visualizations side by side. Here's the complete solution: ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Maze Pathfinding Visualizer</title> <style> body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c); margin: 0; padding: 20px; min-height: 100vh; display: flex; flex-direction: column; align-items: center; color: white; overflow-x: hidden; } .header { text-align: center; margin-bottom: 20px; width: 100%; } h1 { font-size: 2.5rem; margin-bottom: 10px; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); letter-spacing: 2px; } .subtitle { font-size: 1.2rem; max-width: 800px; margin: 0 auto 20px; color: #e0e0ff; line-height: 1.6; } .container { display: flex; justify-content: center; gap: 40px; flex-wrap: wrap; margin-bottom: 20px; } .maze-container { background: rgba(0, 0, 30, 0.7); border-radius: 15px; padding: 20px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); backdrop-filter: blur(5px); border: 2px solid rgba(255, 255, 255, 0.1); transition: transform 0.3s ease; } .maze-container:hover { transform: translateY(-5px); } .algorithm-title { text-align: center; font-size: 1.8rem; margin-bottom: 15px; color: #ffcc00; font-weight: bold; text-shadow: 0 0 8px rgba(255, 204, 0, 0.5); } canvas { background-color: #0a0a2a; border-radius: 8px; display: block; box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5); } .stats { text-align: center; margin-top: 15px; font-size: 1.2rem; font-weight: bold; color: #4dffb8; height: 30px; } .controls { display: flex; gap: 20px; margin-top: 20px; flex-wrap: wrap; justify-content: center; } button { background: linear-gradient(to right, #ff8c00, #ff0080); color: white; border: none; padding: 12px 25px; font-size: 1.1rem; border-radius: 50px; cursor: pointer; transition: all 0.3s ease; font-weight: bold; letter-spacing: 1px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3); } button:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4); } button:active { transform: translateY(1px); } .legend { display: flex; justify-content: center; gap: 30px; margin-top: 20px; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; gap: 8px; font-size: 1rem; } .legend-color { width: 20px; height: 20px; border-radius: 4px; } .footer { margin-top: 30px; text-align: center; color: #a0a0ff; font-size: 0.9rem; max-width: 800px; } @media (max-width: 900px) { .container { flex-direction: column; align-items: center; } .maze-container { width: 90%; } } </style> </head> <body> <div class="header"> <h1>Maze Pathfinding Visualizer</h1> <p class="subtitle">Visual comparison of A* and Dijkstra's algorithms solving randomly generated 10x10 mazes. Watch as each algorithm explores paths step-by-step to find the optimal route from start to finish.</p> </div> <div class="container"> <div class="maze-container"> <div class="algorithm-title">A* Algorithm</div> <canvas id="maze1" width="400" height="400"></canvas> <div class="stats" id="stats1">Generating maze...</div> </div> <div class="maze-container"> <div class="algorithm-title">Dijkstra's Algorithm</div> <canvas id="maze2" width="400" height="400"></canvas> <div class="stats" id="stats2">Generating maze...</div> </div> </div> <div class="controls"> <button id="generateBtn">Generate New Mazes</button> <button id="solveBtn">Solve Mazes</button> </div> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background-color: #4CAF50;"></div> <span>Start</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #F44336;"></div> <span>End</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #2196F3;"></div> <span>Visited</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #FFEB3B;"></div> <span>Path</span> </div> <div class="legend-item"> <div class="legend-color" style="background-color: #9C27B0;"></div> <span>Frontier</span> </div> </div> <div class="footer"> <p>A* uses heuristics to prioritize promising paths, while Dijkstra's explores all directions equally. Notice how A* typically finds the solution faster by directing its search toward the goal.</p> </div> <script> // Maze and algorithm parameters const MAZE_SIZE = 10; const CELL_SIZE = 40; const ANIMATION_DELAY = 50; // Colors const COLORS = { WALL: '#101030', PATH: '#0a0a2a', START: '#4CAF50', END: '#F44336', VISITED: '#2196F3', SOLUTION_PATH: '#FFEB3B', FRONTIER: '#9C27B0', TEXT: '#FFFFFF' }; // Maze class class Maze { constructor(size) { this.size = size; this.grid = this.generateMaze(); this.start = { x: 0, y: 0 }; this.end = { x: size - 1, y: size - 1 }; } generateMaze() { // Initialize grid with all walls const grid = Array(this.size).fill().map(() => Array(this.size).fill().map(() => ({ top: true, right: true, bottom: true, left: true, visited: false })) ); // Use Depth-First Search to generate maze const stack = []; const startCell = { x: 0, y: 0 }; grid[startCell.y][startCell.x].visited = true; stack.push(startCell); while (stack.length > 0) { const current = stack.pop(); const neighbors = this.getUnvisitedNeighbors(current, grid); if (neighbors.length > 0) { stack.push(current); const randomNeighbor = neighbors[Math.floor(Math.random() * neighbors.length)]; // Remove wall between current and neighbor if (randomNeighbor.x === current.x) { if (randomNeighbor.y > current.y) { grid[current.y][current.x].bottom = false; grid[randomNeighbor.y][randomNeighbor.x].top = false; } else { grid[current.y][current.x].top = false; grid[randomNeighbor.y][randomNeighbor.x].bottom = false; } } else { if (randomNeighbor.x > current.x) { grid[current.y][current.x].right = false; grid[randomNeighbor.y][randomNeighbor.x].left = false; } else { grid[current.y][current.x].left = false; grid[randomNeighbor.y][randomNeighbor.x].right = false; } } grid[randomNeighbor.y][randomNeighbor.x].visited = true; stack.push(randomNeighbor); } } // Reset visited flags for pathfinding for (let y = 0; y < this.size; y++) { for (let x = 0; x < this.size; x++) { grid[y][x].visited = false; } } return grid; } getUnvisitedNeighbors(cell, grid) { const neighbors = []; const { x, y } = cell; const directions = [ { x: x, y: y - 1, dir: 'top' }, { x: x + 1, y: y, dir: 'right' }, { x: x, y: y + 1, dir: 'bottom' }, { x: x - 1, y: y, dir: 'left' } ]; for (const dir of directions) { if (dir.x >= 0 && dir.x < this.size && dir.y >= 0 && dir.y < this.size && !grid[dir.y][dir.x].visited) { neighbors.push({ x: dir.x, y: dir.y }); } } return neighbors; } getNeighbors(cell) { const neighbors = []; const { x, y } = cell; if (!this.grid[y][x].top && y > 0) neighbors.push({ x, y: y - 1 }); if (!this.grid[y][x].right && x < this.size - 1) neighbors.push({ x: x + 1, y }); if (!this.grid[y][x].bottom && y < this.size - 1) neighbors.push({ x, y: y + 1 }); if (!this.grid[y][x].left && x > 0) neighbors.push({ x: x - 1, y }); return neighbors; } } // Pathfinder class class Pathfinder { constructor(maze, canvasId, algorithm) { this.maze = maze; this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.algorithm = algorithm; this.statsElement = document.getElementById(`stats${canvasId.slice(-1)}`); this.reset(); } reset() { this.frontier = []; this.cameFrom = {}; this.costSoFar = {}; this.solutionPath = []; this.visited = []; this.solved = false; this.startTime = null; this.endTime = null; const startKey = this.key(this.maze.start); this.frontier.push({ cell: this.maze.start, priority: 0 }); this.cameFrom[startKey] = null; this.costSoFar[startKey] = 0; // Reset visited flags for (let y = 0; y < this.maze.size; y++) { for (let x = 0; x < this.maze.size; x++) { this.maze.grid[y][x].visited = false; } } this.drawMaze(); } key(cell) { return `${cell.x},${cell.y}`; } heuristic(a, b) { // Manhattan distance return Math.abs(a.x - b.x) + Math.abs(a.y - b.y); } step() { if (this.frontier.length === 0 || this.solved) { if (!this.solved && this.frontier.length === 0) { this.statsElement.textContent = "No solution found!"; } return false; } if (!this.startTime) { this.startTime = performance.now(); } // Get next cell based on algorithm let current; if (this.algorithm === 'astar') { this.frontier.sort((a, b) => a.priority - b.priority); current = this.frontier.shift().cell; } else { // dijkstra this.frontier.sort((a, b) => a.priority - b.priority); current = this.frontier.shift().cell; } // Mark as visited this.maze.grid[current.y][current.x].visited = true; this.visited.push(current); // Check if we reached the end if (current.x === this.maze.end.x && current.y === this.maze.end.y) { this.solved = true; this.endTime = performance.now(); this.reconstructPath(); this.drawMaze(); const timeTaken = (this.endTime - this.startTime).toFixed(2); this.statsElement.textContent = `Solved in ${timeTaken} ms | Path length: ${this.solutionPath.length}`; return false; } // Explore neighbors const neighbors = this.maze.getNeighbors(current); for (const next of neighbors) { const newCost = this.costSoFar[this.key(current)] + 1; const nextKey = this.key(next); if (this.costSoFar[nextKey] === undefined || newCost < this.costSoFar[nextKey]) { this.costSoFar[nextKey] = newCost; let priority = newCost; if (this.algorithm === 'astar') { priority += this.heuristic(this.maze.end, next); } this.frontier.push({ cell: next, priority }); this.cameFrom[nextKey] = current; } } this.drawMaze(); return true; } reconstructPath() { let current = this.maze.end; while (current) { this.solutionPath.push(current); current = this.cameFrom[this.key(current)]; } this.solutionPath.reverse(); } drawMaze() { const ctx = this.ctx; const size = this.maze.size; const cellSize = CELL_SIZE; // Clear canvas ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // Draw background ctx.fillStyle = COLORS.PATH; ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Draw walls and paths for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const cell = this.maze.grid[y][x]; const xPos = x * cellSize; const yPos = y * cellSize; // Draw walls ctx.strokeStyle = COLORS.WALL; ctx.lineWidth = 2; if (cell.top) { ctx.beginPath(); ctx.moveTo(xPos, yPos); ctx.lineTo(xPos + cellSize, yPos); ctx.stroke(); } if (cell.right) { ctx.beginPath(); ctx.moveTo(xPos + cellSize, yPos); ctx.lineTo(xPos + cellSize, yPos + cellSize); ctx.stroke(); } if (cell.bottom) { ctx.beginPath(); ctx.moveTo(xPos, yPos + cellSize); ctx.lineTo(xPos + cellSize, yPos + cellSize); ctx.stroke(); } if (cell.left) { ctx.beginPath(); ctx.moveTo(xPos, yPos); ctx.lineTo(xPos, yPos + cellSize); ctx.stroke(); } } } // Draw visited cells for (const cell of this.visited) { const xPos = cell.x * cellSize + 2; const yPos = cell.y * cellSize + 2; const size = cellSize - 4; ctx.fillStyle = COLORS.VISITED; ctx.fillRect(xPos, yPos, size, size); } // Draw frontier for (const item of this.frontier) { const cell = item.cell; const xPos = cell.x * cellSize + 2; const yPos = cell.y * cellSize + 2; const size = cellSize - 4; ctx.fillStyle = COLORS.FRONTIER; ctx.fillRect(xPos, yPos, size, size); } // Draw solution path for (const cell of this.solutionPath) { const xPos = cell.x * cellSize + 2; const yPos = cell.y * cellSize + 2; const size = cellSize - 4; ctx.fillStyle = COLORS.SOLUTION_PATH; ctx.fillRect(xPos, yPos, size, size); } // Draw start and end this.drawCell(this.maze.start, COLORS.START); this.drawCell(this.maze.end, COLORS.END); } drawCell(cell, color) { const ctx = this.ctx; const xPos = cell.x * CELL_SIZE + 2; const yPos = cell.y * CELL_SIZE + 2; const size = CELL_SIZE - 4; ctx.fillStyle = color; ctx.fillRect(xPos, yPos, size, size); // Draw inner circle ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; ctx.beginPath(); ctx.arc( cell.x * CELL_SIZE + CELL_SIZE / 2, cell.y * CELL_SIZE + CELL_SIZE / 2, CELL_SIZE / 4, 0, Math.PI * 2 ); ctx.fill(); } } // Initialize application let maze1, maze2; let astarSolver, dijkstraSolver; let animationRunning = false; function init() { generateMazes(); document.getElementById('solveBtn').addEventListener('click', solveMazes); document.getElementById('generateBtn').addEventListener('click', generateMazes); } function generateMazes() { if (animationRunning) return; // Create new mazes maze1 = new Maze(MAZE_SIZE); maze2 = new Maze(MAZE_SIZE); // Create solvers astarSolver = new Pathfinder(maze1, 'maze1', 'astar'); dijkstraSolver = new Pathfinder(maze2, 'maze2', 'dijkstra'); // Update stats document.getElementById('stats1').textContent = 'Ready to solve'; document.getElementById('stats2').textContent = 'Ready to solve'; } function solveMazes() { if (animationRunning) return; animationRunning = true; document.getElementById('stats1').textContent = 'Solving...'; document.getElementById('stats2').textContent = 'Solving...'; // Reset solvers with current mazes astarSolver.reset(); dijkstraSolver.reset(); // Animation function function animate() { const astarRunning = astarSolver.step(); const dijkstraRunning = dijkstraSolver.step(); if (astarRunning || dijkstraRunning) { setTimeout(animate, ANIMATION_DELAY); } else { animationRunning = false; } } animate(); } // Start when page loads window.addEventListener('load', init); </script> </body> </html> ``` ## Features 1. **Maze Generation**: - Creates two different 10x10 mazes using Depth-First Search algorithm - Each maze has a start (top-left) and end (bottom-right) position 2. **Pathfinding Algorithms**: - A* algorithm (left canvas) - uses heuristics to find optimal path - Dijkstra's algorithm (right canvas) - explores all paths equally - Visualizes each step of the pathfinding process 3. **Visual Design**: - Space-themed gradient background with glass-like maze containers - Color-coded visualization: - Green: Start position - Red: End position - Blue: Visited cells - Yellow: Solution path - Purple: Frontier (cells to be explored) - Smooth animations showing algorithm progression - Legend explaining color scheme 4. **Statistics**: - Shows solving time for each algorithm - Displays path length for comparison - Updates in real-time during solving 5. **Controls**: - "Generate New Mazes" button creates new random mazes - "Solve Mazes" button starts the pathfinding visualization The visualization clearly demonstrates how A* typically outperforms Dijkstra's algorithm by using heuristics to guide its search toward the goal.
Drag to resize
An elegant and visually appealing solution for comparing A* and Dijkstra's pathfinding algorithms. Here is a fully self-contained HTML file that generates random mazes and visualizes the solving process side-by-side. Click the **"Generate & Solve New Maze"** button to start a new simulation. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pathfinding Visualizer</title> <style> :root { --bg-color: #1a202c; --text-color: #e2e8f0; --canvas-bg: #2d3748; --wall-color: #1a202c; --start-color: #48bb78; --end-color: #f56565; --path-color: #f6e05e; --astar-visited-color: #b794f4; --dijkstra-visited-color: #63b3ed; --button-bg: #4299e1; --button-hover-bg: #63b3ed; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background-color: var(--bg-color); color: var(--text-color); margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; } h1 { font-weight: 300; letter-spacing: 1px; margin-bottom: 20px; } .controls { margin-bottom: 30px; } #generateButton { background-color: var(--button-bg); color: white; border: none; padding: 12px 24px; font-size: 16px; border-radius: 8px; cursor: pointer; transition: background-color 0.2s ease-in-out; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } #generateButton:hover { background-color: var(--button-hover-bg); } .container { display: flex; flex-wrap: wrap; gap: 40px; justify-content: center; align-items: flex-start; } .maze-container { display: flex; flex-direction: column; align-items: center; gap: 15px; } canvas { background-color: var(--canvas-bg); border-radius: 8px; box-shadow: 0 10px 15px rgba(0, 0, 0, 0.2); } h2 { font-weight: 400; margin: 0; } .timer { font-size: 1.1em; font-family: 'Courier New', Courier, monospace; background-color: var(--canvas-bg); padding: 8px 16px; border-radius: 6px; min-width: 180px; text-align: center; } </style> </head> <body> <h1>Pathfinding Algorithm Comparison</h1> <div class="controls"> <button id="generateButton">Generate & Solve New Maze</button> </div> <div class="container"> <div class="maze-container"> <h2>A* Search</h2> <canvas id="canvasAStar" width="400" height="400"></canvas> <div id="timeAStar" class="timer">Time: -- ms</div> </div> <div class="maze-container"> <h2>Dijkstra's Algorithm</h2> <canvas id="canvasDijkstra" width="400" height="400"></canvas> <div id="timeDijkstra" class="timer">Time: -- ms</div> </div> </div> <script> // --- SETUP --- const COLS = 10; const ROWS = 10; const canvasAStar = document.getElementById('canvasAStar'); const ctxAStar = canvasAStar.getContext('2d'); const canvasDijkstra = document.getElementById('canvasDijkstra'); const ctxDijkstra = canvasDijkstra.getContext('2d'); const generateButton = document.getElementById('generateButton'); const timeAStarEl = document.getElementById('timeAStar'); const timeDijkstraEl = document.getElementById('timeDijkstra'); const CELL_SIZE = canvasAStar.width / COLS; const WALL_THICKNESS = Math.max(2, CELL_SIZE / 10); const colors = { wall: getComputedStyle(document.documentElement).getPropertyValue('--wall-color').trim(), start: getComputedStyle(document.documentElement).getPropertyValue('--start-color').trim(), end: getComputedStyle(document.documentElement).getPropertyValue('--end-color').trim(), path: getComputedStyle(document.documentElement).getPropertyValue('--path-color').trim(), astarVisited: getComputedStyle(document.documentElement).getPropertyValue('--astar-visited-color').trim(), dijkstraVisited: getComputedStyle(document.documentElement).getPropertyValue('--dijkstra-visited-color').trim(), bg: getComputedStyle(document.documentElement).getPropertyValue('--canvas-bg').trim() }; let maze; let animationFrameId; let animationSpeed = 20; // ms per step // --- DATA STRUCTURES --- class Cell { constructor(i, j) { this.i = i; this.j = j; this.walls = [true, true, true, true]; // top, right, bottom, left this.visited = false; } } // Node for pathfinding algorithms class Node { constructor(x, y) { this.x = x; this.y = y; this.g = Infinity; // Cost from start this.h = 0; // Heuristic cost to end this.f = Infinity; // g + h this.parent = null; this.isWall = false; } } // Simple Priority Queue implementation using a sorted array class PriorityQueue { constructor() { this.items = []; } enqueue(item, priority) { item.f = priority; // Using f-score as priority this.items.push(item); this.items.sort((a, b) => a.f - b.f); } dequeue() { return this.items.shift(); } isEmpty() { return this.items.length === 0; } } // --- MAZE GENERATION (Recursive Backtracker) --- function generateMaze(cols, rows) { const grid = Array.from({ length: rows }, (_, j) => Array.from({ length: cols }, (_, i) => new Cell(i, j)) ); const stack = []; let current = grid[0][0]; current.visited = true; function getUnvisitedNeighbors(cell) { const neighbors = []; const { i, j } = cell; // Top, Right, Bottom, Left const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]]; for (const [dx, dy] of directions) { const ni = i + dx; const nj = j + dy; if (ni >= 0 && ni < cols && nj >= 0 && nj < rows && !grid[nj][ni].visited) { neighbors.push(grid[nj][ni]); } } return neighbors.length > 0 ? neighbors[Math.floor(Math.random() * neighbors.length)] : null; } function removeWalls(a, b) { const dx = a.i - b.i; if (dx === 1) { // a is right of b a.walls[3] = false; b.walls[1] = false; } else if (dx === -1) { // a is left of b a.walls[1] = false; b.walls[3] = false; } const dy = a.j - b.j; if (dy === 1) { // a is below b a.walls[0] = false; b.walls[2] = false; } else if (dy === -1) { // a is above b a.walls[2] = false; b.walls[0] = false; } } while (true) { const next = getUnvisitedNeighbors(current); if (next) { next.visited = true; stack.push(current); removeWalls(current, next); current = next; } else if (stack.length > 0) { current = stack.pop(); } else { break; } } return grid; } // --- PATHFINDING ALGORITHMS --- // Heuristic for A* (Manhattan distance) function manhattanDistance(node, goal) { return Math.abs(node.x - goal.x) + Math.abs(node.y - goal.y); } // Universal solver function function solve(mazeData, heuristic) { const startNode = new Node(0, 0); const endNode = new Node(COLS - 1, ROWS - 1); const grid = Array.from({ length: ROWS }, (_, y) => Array.from({ length: COLS }, (_, x) => new Node(x, y)) ); const openSet = new PriorityQueue(); const visitedSteps = []; grid[0][0].g = 0; grid[0][0].h = heuristic(grid[0][0], endNode); grid[0][0].f = grid[0][0].h; openSet.enqueue(grid[0][0], grid[0][0].f); while (!openSet.isEmpty()) { const current = openSet.dequeue(); if (current.x === endNode.x && current.y === endNode.y) { // Path found, reconstruct it const path = []; let temp = current; while (temp) { path.push(temp); temp = temp.parent; } return { path: path.reverse(), visited: visitedSteps }; } if (current.x !== startNode.x || current.y !== startNode.y) { visitedSteps.push(current); } const mazeCell = mazeData[current.y][current.x]; // Top, Right, Bottom, Left const directions = [[0, -1], [1, 0], [0, 1], [-1, 0]]; for (let i = 0; i < 4; i++) { if (!mazeCell.walls[i]) { const [dx, dy] = directions[i]; const nx = current.x + dx; const ny = current.y + dy; if (nx >= 0 && nx < COLS && ny >= 0 && ny < ROWS) { const neighbor = grid[ny][nx]; const tentativeG = current.g + 1; // All edge weights are 1 if (tentativeG < neighbor.g) { neighbor.parent = current; neighbor.g = tentativeG; neighbor.h = heuristic(neighbor, endNode); neighbor.f = neighbor.g + neighbor.h; openSet.enqueue(neighbor, neighbor.f); } } } } } return { path: [], visited: visitedSteps }; // No path found } // --- DRAWING & ANIMATION --- function drawCell(ctx, cell, color) { const { i, j } = cell; ctx.fillStyle = color; ctx.fillRect(i * CELL_SIZE + WALL_THICKNESS / 2, j * CELL_SIZE + WALL_THICKNESS / 2, CELL_SIZE - WALL_THICKNESS, CELL_SIZE - WALL_THICKNESS); } function drawMaze(ctx, mazeGrid) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillStyle = colors.bg; ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height); for (let j = 0; j < ROWS; j++) { for (let i = 0; i < COLS; i++) { const cell = mazeGrid[j][i]; const x = i * CELL_SIZE; const y = j * CELL_SIZE; ctx.strokeStyle = colors.wall; ctx.lineWidth = WALL_THICKNESS; if (cell.walls[0]) ctx.strokeRect(x, y, CELL_SIZE, 0); // Top if (cell.walls[1]) ctx.strokeRect(x + CELL_SIZE, y, 0, CELL_SIZE); // Right if (cell.walls[2]) ctx.strokeRect(x, y + CELL_SIZE, CELL_SIZE, 0); // Bottom if (cell.walls[3]) ctx.strokeRect(x, y, 0, CELL_SIZE); // Left } } drawCell(ctx, {i: 0, j: 0}, colors.start); drawCell(ctx, {i: COLS - 1, j: ROWS - 1}, colors.end); } function animate(aStarResult, dijkstraResult) { let visitedIndex = 0; let pathIndex = 0; let phase = 'visited'; // 'visited' or 'path' const maxVisitedLength = Math.max(aStarResult.visited.length, dijkstraResult.visited.length); function step() { if (phase === 'visited') { if (visitedIndex < maxVisitedLength) { if (visitedIndex < aStarResult.visited.length) { const node = aStarResult.visited[visitedIndex]; drawCell(ctxAStar, { i: node.x, j: node.y }, colors.astarVisited); } if (visitedIndex < dijkstraResult.visited.length) { const node = dijkstraResult.visited[visitedIndex]; drawCell(ctxDijkstra, { i: node.x, j: node.y }, colors.dijkstraVisited); } visitedIndex++; } else { // Redraw start/end on top of visited squares drawCell(ctxAStar, {i: 0, j: 0}, colors.start); drawCell(ctxAStar, {i: COLS - 1, j: ROWS - 1}, colors.end); drawCell(ctxDijkstra, {i: 0, j: 0}, colors.start); drawCell(ctxDijkstra, {i: COLS - 1, j: ROWS - 1}, colors.end); phase = 'path'; } } else if (phase === 'path') { if (pathIndex < dijkstraResult.path.length) { // Both paths are same length const aStarNode = aStarResult.path[pathIndex]; const dijkstraNode = dijkstraResult.path[pathIndex]; if(pathIndex > 0 && pathIndex < dijkstraResult.path.length -1) { drawCell(ctxAStar, { i: aStarNode.x, j: aStarNode.y }, colors.path); drawCell(ctxDijkstra, { i: dijkstraNode.x, j: dijkstraNode.y }, colors.path); } pathIndex++; } else { cancelAnimationFrame(animationFrameId); generateButton.disabled = false; generateButton.textContent = "Generate & Solve New Maze"; return; // Animation finished } } animationFrameId = setTimeout(() => { requestAnimationFrame(step); }, animationSpeed); } animationFrameId = requestAnimationFrame(step); } // --- MAIN LOGIC --- function setup() { if (animationFrameId) { clearTimeout(animationFrameId); } generateButton.disabled = true; generateButton.textContent = "Working..."; timeAStarEl.textContent = 'Time: -- ms'; timeDijkstraEl.textContent = 'Time: -- ms'; maze = generateMaze(COLS, ROWS); drawMaze(ctxAStar, maze); drawMaze(ctxDijkstra, maze); // Run solvers to get data and timing (no drawing) const t0_astar = performance.now(); const aStarResult = solve(maze, manhattanDistance); const t1_astar = performance.now(); timeAStarEl.textContent = `Time: ${(t1_astar - t0_astar).toFixed(3)} ms`; const dijkstraHeuristic = () => 0; // Dijkstra is A* with h=0 const t0_dijkstra = performance.now(); const dijkstraResult = solve(maze, dijkstraHeuristic); const t1_dijkstra = performance.now(); timeDijkstraEl.textContent = `Time: ${(t1_dijkstra - t0_dijkstra).toFixed(3)} ms`; animate(aStarResult, dijkstraResult); } generateButton.addEventListener('click', setup); window.onload = setup; </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Maze Generator & Path-finding Visualizer</title> <style> body{ margin:0; font-family:Arial, Helvetica, sans-serif; background:#1e1e1e; color:#eee; display:flex; flex-direction:column; align-items:center; justify-content:center; height:100vh; overflow:hidden; } #container{ display:flex; gap:40px; } canvas{ border:2px solid #eee; background:#111; box-shadow:0 0 15px rgba(0,0,0,.9); } h1{ margin-bottom:12px; font-weight:400; } </style> </head> <body> <h1>Maze Generator & Path-finding Visualizer (10 × 10)</h1> <div id="container"> <canvas id="astar" width="420" height="460"></canvas> <canvas id="dijkstra" width="420" height="460"></canvas> </div> <script> /* ---------- SETTINGS ---------- */ const COLS = 10, ROWS = 10; const CELL = 40; // size in px of a cell const OFFSET_TOP = 20; // top space for title inside canvas const STEP_DELAY = 0; // 0 = one step every frame (60 fps) /* ------------------------------ */ class Cell{ constructor(x,y){ this.x=x; this.y=y; this.walls=[true,true,true,true]; // top,right,bottom,left this.visited=false; } draw(ctx){ const px=this.x*CELL, py=this.y*CELL+OFFSET_TOP; ctx.strokeStyle="#eee"; ctx.lineWidth=2; if(this.walls[0]) line(px,py,px+CELL,py,ctx); // top if(this.walls[1]) line(px+CELL,py,px+CELL,py+CELL,ctx); // right if(this.walls[2]) line(px+CELL,py+CELL,px,py+CELL,ctx); // bottom if(this.walls[3]) line(px,py+CELL,px,py,ctx); // left } } function line(x1,y1,x2,y2,ctx){ ctx.beginPath(); ctx.moveTo(x1,y1); ctx.lineTo(x2,y2); ctx.stroke(); } class Maze{ constructor(cols,rows){ this.cols=cols; this.rows=rows; this.grid=[]; for(let y=0;y<rows;y++){ for(let x=0;x<cols;x++) this.grid.push(new Cell(x,y)); } this.generate(); } index(x,y){ return (x<0||y<0||x>=this.cols||y>=this.rows)?-1:y*this.cols+x; } get(x,y){ return this.grid[this.index(x,y)]; } neighbors(cell){ const deltas=[[0,-1,0,2],[1,0,1,3],[0,1,2,0],[-1,0,3,1]]; const list=[]; deltas.forEach(d=>{ const nx=cell.x+d[0], ny=cell.y+d[1]; const ni=this.index(nx,ny); if(ni!==-1) list.push({cell:this.grid[ni], dir:d[2], opp:d[3]}); }); return list; } generate(){ const stack=[]; const start=this.get(0,0); start.visited=true; stack.push(start); while(stack.length){ const current=stack.pop(); const unvisited=this.neighbors(current).filter(n=>!n.cell.visited); if(unvisited.length){ stack.push(current); const pick=unvisited[Math.floor(Math.random()*unvisited.length)]; current.walls[pick.dir]=false; pick.cell.walls[pick.opp]=false; pick.cell.visited=true; stack.push(pick.cell); } } // reset visited flags (used by generators) this.grid.forEach(c=>c.visited=false); } draw(ctx){ this.grid.forEach(c=>c.draw(ctx)); } } /* ---------- PATHFINDERS ---------- */ class Solver{ constructor(maze,heuristic,title,canvas){ this.maze=maze; this.heuristic=heuristic; // function(a,b) -> h this.title=title; this.canvas=canvas; this.ctx=canvas.getContext("2d"); this.open=[]; this.closed=new Set(); this.cameFrom=new Map(); this.g=new Map(); this.f=new Map(); this.start=maze.get(0,0); this.goal=maze.get(COLS-1,ROWS-1); this.open.push(this.start); this.g.set(this.start,0); this.f.set(this.start,this.heuristic(this.start,this.goal)); this.solved=false; this.path=[]; this.startTime=null; this.endTime=null; } key(c){ return c.x+","+c.y; } step(){ if(this.solved) return; if(!this.startTime) this.startTime=performance.now(); if(!this.open.length){ this.solved=true; return; } // choose node with lowest f let idx=0; for(let i=1;i<this.open.length;i++){ if(this.f.get(this.open[i])<this.f.get(this.open[idx])) idx=i; } const current=this.open.splice(idx,1)[0]; if(current===this.goal){ this.reconstructPath(current); this.solved=true; this.endTime=performance.now(); return; } this.closed.add(current); const neighbors=this.expandNeighbors(current); neighbors.forEach(n=>{ if(this.closed.has(n)) return; const tentative=this.g.get(current)+1; // cost between adj cells =1 if(!this.open.includes(n) || tentative<this.g.get(n)){ this.cameFrom.set(n,current); this.g.set(n,tentative); const fscore=tentative+this.heuristic(n,this.goal); this.f.set(n,fscore); if(!this.open.includes(n)) this.open.push(n); } }); } expandNeighbors(c){ const list=[]; // up,right,down,left const dirs=[[0,-1,0],[1,0,1],[0,1,2],[-1,0,3]]; dirs.forEach(d=>{ if(!c.walls[d[2]]){ const nx=c.x+d[0], ny=c.y+d[1]; const neighbor=this.maze.get(nx,ny); list.push(neighbor); } }); return list; } reconstructPath(current){ this.path=[current]; while(this.cameFrom.has(current)){ current=this.cameFrom.get(current); this.path.push(current); } } draw(){ const ctx=this.ctx; ctx.clearRect(0,0,this.canvas.width,this.canvas.height); this.maze.draw(ctx); // draw closed (visited) nodes ctx.fillStyle="rgba(0,150,255,0.3)"; this.closed.forEach(c=>fillCell(c,ctx)); // draw open set ctx.fillStyle="rgba(255,255,0,0.4)"; this.open.forEach(c=>fillCell(c,ctx)); // draw current path if solved if(this.solved){ ctx.fillStyle="rgba(0,255,100,0.6)"; this.path.forEach(c=>fillCell(c,ctx)); } // start & goal ctx.fillStyle="#00f"; fillCell(this.start,ctx); ctx.fillStyle="#f00"; fillCell(this.goal,ctx); // text ctx.fillStyle="#eee"; ctx.font="16px Arial"; ctx.textAlign="center"; ctx.fillText(this.title, this.canvas.width/2,15); if(this.solved){ const t=(this.endTime-this.startTime).toFixed(2); ctx.fillText(`Time: ${t} ms`, this.canvas.width/2, this.canvas.height-5); } } } function fillCell(cell,ctx){ const px=cell.x*CELL+2, py=cell.y*CELL+OFFSET_TOP+2; ctx.fillRect(px,py,CELL-3,CELL-3); } function manhattan(a,b){ return Math.abs(a.x-b.x)+Math.abs(a.y-b.y); } /* ---------- SETUP ---------- */ const mazeA=new Maze(COLS,ROWS); const mazeB=new Maze(COLS,ROWS); const solverA=new Solver(mazeA,manhattan,"A* Search",document.getElementById("astar")); const solverB=new Solver(mazeB,()=>0,"Dijkstra's",document.getElementById("dijkstra")); let last=0; function animate(ts){ if(ts-last>=STEP_DELAY){ solverA.step(); solverB.step(); last=ts; } solverA.draw(); solverB.draw(); requestAnimationFrame(animate); } requestAnimationFrame(animate); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Maze Generator & Pathfinding Visualizer</title> <style> body { background: #21222c; color: #f8f8f2; font-family: "Segoe UI", Verdana, Monaco, "Fira Mono", monospace; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; min-height: 100vh; margin: 0; } h1 { margin: 20px 0 10px 0; letter-spacing: 1px; font-weight: 600; } .mazes-container { display: flex; gap: 32px; margin: 0 0 24px 0; } .canvas-container { display: flex; flex-direction: column; align-items: center; background: #292a36; border-radius: 12px; box-shadow: 0 6px 32px #0008; padding: 18px 24px 10px 24px; } .algo-title { font-weight: 600; font-size: 1.08em; margin-bottom: 8px; letter-spacing: 0.03em; color: #50fa7b; text-shadow: 0 2px 16px #444a; } .algo-title.dijkstra { color: #8be9fd; } canvas { background: #181920; border-radius: 8px; margin-bottom: 12px; border: 2px solid #313347; box-shadow: 0 2px 12px #0007; } .stat { font-size: 0.95em; margin-top: 2px; color: #bdbdbd; } button { background: linear-gradient(90deg, #50fa7b 0%, #8be9fd 100%); border: none; color: #21222c; font-weight: 700; padding: 10px 28px; border-radius: 6px; cursor: pointer; font-size: 1.08em; box-shadow: 0 2px 12px #2224; margin-top: 10px; margin-bottom: 10px; transition: background 0.2s, color 0.2s; } button:hover { background: linear-gradient(90deg, #8be9fd 0%, #50fa7b 100%); color: #21222c; } .legend { margin: 8px 0 0 0; display: flex; gap: 26px; font-size: 0.98em; justify-content: center; align-items: center; } .legend-box { width: 16px; height: 16px; display: inline-block; border-radius: 3px; border: 1px solid #313347; margin-right: 6px; vertical-align: -4px; } .wall { background: #44475a !important; } .start { background: #f1fa8c !important; } .goal { background: #ff5555 !important; } .open { background: #8be9fd60 !important; border-color: #8be9fd !important;} .closed { background: #bd93f960 !important; border-color: #bd93f9 !important;} .path { background: #50fa7bb0 !important; border-color: #50fa7b !important;} </style> </head> <body> <h1>Maze Generator & Pathfinding Visualizer</h1> <div class="legend"> <span><span class="legend-box wall"></span>Wall</span> <span><span class="legend-box start"></span>Start</span> <span><span class="legend-box goal"></span>Goal</span> <span><span class="legend-box open"></span>Frontier</span> <span><span class="legend-box closed"></span>Visited</span> <span><span class="legend-box path"></span>Path</span> </div> <button id="regenerate">Regenerate</button> <div class="mazes-container"> <div class="canvas-container"> <div class="algo-title">A* Algorithm</div> <canvas id="canvasAStar" width="340" height="340"></canvas> <div class="stat" id="timeAStar">Time: -- ms</div> </div> <div class="canvas-container"> <div class="algo-title dijkstra">Dijkstra's Algorithm</div> <canvas id="canvasDijkstra" width="340" height="340"></canvas> <div class="stat" id="timeDijkstra">Time: -- ms</div> </div> </div> <script> // ---- CONFIGS ---- const MAZE_SIZE = 10; const CELL_SIZE = 32; // px const ANIMATION_SPEED = 26; // ms per frame const WALL_COLOR = "#44475a"; const OPEN_COLOR = "#8be9fd"; const CLOSED_COLOR = "#bd93f9"; const PATH_COLOR = "#50fa7b"; const START_COLOR = "#f1fa8c"; const GOAL_COLOR = "#ff5555"; // Helper function shuffle(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } // Maze Generation - Recursive Backtracker function generateMaze(n) { const maze = Array.from({length: n}, () => Array.from({length: n}, () => ({walls: [1,1,1,1], visited: false}))); const dir = [ [0,-1], // up [1,0], // right [0,1], // down [-1,0], // left ]; function breakWall(x, y, nx, ny, d) { // d = 0:up, 1:right, 2:down, 3:left maze[y][x].walls[d] = 0; maze[ny][nx].walls[(d+2)%4] = 0; } function dfs(x, y) { maze[y][x].visited = true; const dirs = shuffle([0,1,2,3]); for (const d of dirs) { const nx = x + dir[d][0]; const ny = y + dir[d][1]; if (nx >= 0 && nx < n && ny >= 0 && ny < n && !maze[ny][nx].visited) { breakWall(x, y, nx, ny, d); dfs(nx, ny); } } } dfs(0, 0); // Always carve from top-left! // Remove visited property for simplicity for (let r of maze) for (let c of r) delete c.visited; // Convert to grid: 0=path, 1=wall // We'll use cell wall refs for drawing, but for pathfinding, let's build the grid: const grid = Array.from({length: n}, () => Array(n).fill(0)); // Optionally, add a few random walls for extra flavor but do not block start/end! let addWalls = Math.floor(n * 2); while (addWalls--) { let rx = Math.floor(Math.random()*n); let ry = Math.floor(Math.random()*n); if ((rx !== 0 || ry !== 0) && (rx !== n-1 || ry !== n-1)) grid[ry][rx] = 1; // wall } return {maze, grid: grid.map(row=>row.slice())}; } // Utilities function reconstructPath(cameFrom, end) { let path = []; let curr = end; while (curr && cameFrom[`${curr[0]},${curr[1]}`]) { path.push(curr); curr = cameFrom[`${curr[0]},${curr[1]}`]; } return path.reverse(); } function* astar(grid, start, goal) { const n = grid.length; function h([x,y]) { // Manhattan return Math.abs(x - goal[0]) + Math.abs(y - goal[1]); } let openSet = [[h(start), 0, start]]; let gScore = Array.from({length: n}, () => Array(n).fill(Infinity)); gScore[start[1]][start[0]] = 0; let open = new Set(); open.add(start.join(",")); let closed = new Set(); let cameFrom = {}; // For animation let openNodes = {}; let closedNodes = {}; while (openSet.length > 0) { openSet.sort((a, b) => (a[0] - b[0]) || (a[1] - b[1])); let [f, g, curr] = openSet.shift(); open.delete(curr.join(",")); closed.add(curr.join(",")); closedNodes[curr.join(",")] = true; if (curr[0] === goal[0] && curr[1] === goal[1]) { // Found goal! return { cameFrom, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: reconstructPath(cameFrom, goal), done: true, }; } let dirs = [[1,0],[-1,0],[0,1],[0,-1]]; for (let [dx,dy] of dirs) { let nx = curr[0]+dx, ny = curr[1]+dy; if (nx<0 || nx>=n || ny<0 || ny>=n) continue; if (grid[ny][nx] === 1) continue; let neighbor = [nx, ny]; if (closed.has(neighbor.join(","))) continue; let ng = gScore[curr[1]][curr[0]] + 1; if (ng < gScore[ny][nx]) { cameFrom[`${nx},${ny}`] = curr; gScore[ny][nx] = ng; let fscore = ng + h([nx,ny]); if (!open.has(neighbor.join(","))) { openSet.push([fscore, ng, neighbor]); open.add(neighbor.join(",")); openNodes[neighbor.join(",")] = true; // For animation } } } yield { cameFrom: {...cameFrom}, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: reconstructPath(cameFrom, curr) }; } return { cameFrom, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: [], done: true, }; } function* dijkstra(grid, start, goal) { const n = grid.length; let openSet = [[0, start]]; let dist = Array.from({length: n}, () => Array(n).fill(Infinity)); dist[start[1]][start[0]] = 0; let open = new Set(); open.add(start.join(",")); let closed = new Set(); let cameFrom = {}; let openNodes = {}; let closedNodes = {}; while (openSet.length > 0) { openSet.sort((a, b) => (a[0] - b[0])); let [d, curr] = openSet.shift(); open.delete(curr.join(",")); closed.add(curr.join(",")); closedNodes[curr.join(",")] = true; if (curr[0] === goal[0] && curr[1] === goal[1]) { return { cameFrom, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: reconstructPath(cameFrom, goal), done: true, }; } let dirs = [[1,0],[-1,0],[0,1],[0,-1]]; for (let [dx,dy] of dirs) { let nx = curr[0]+dx, ny = curr[1]+dy; if (nx<0 || nx>=n || ny<0 || ny>=n) continue; if (grid[ny][nx] === 1) continue; let alt = dist[curr[1]][curr[0]] + 1; if (alt < dist[ny][nx]) { cameFrom[`${nx},${ny}`] = curr; dist[ny][nx] = alt; if (!open.has([nx,ny].join(","))) { openSet.push([alt, [nx,ny]]); open.add([nx,ny].join(",")); openNodes[[nx,ny].join(",")] = true; // For animation } } } yield { cameFrom: {...cameFrom}, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: reconstructPath(cameFrom, curr) }; } return { cameFrom, closedNodes: {...closedNodes}, openNodes: {...openNodes}, path: [], done: true, }; } // --------- Drawing Stuff --------- function drawMaze(ctx, mazeObj, grid, pathState, start, goal) { ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height); const n = mazeObj.length; // Draw cells (walls) for (let y=0; y<n; ++y) { for (let x=0; x<n; ++x) { const cx = x*CELL_SIZE; const cy = y*CELL_SIZE; // Floor ctx.fillStyle = "#272733"; ctx.fillRect(cx, cy, CELL_SIZE, CELL_SIZE); // Walls if (grid[y][x] === 1) { ctx.fillStyle = WALL_COLOR; ctx.fillRect(cx, cy, CELL_SIZE, CELL_SIZE); continue; } // Pathfinding animations const key = `${x},${y}`; if (key in pathState.openNodes) { ctx.fillStyle = OPEN_COLOR+"60"; ctx.fillRect(cx+3, cy+3, CELL_SIZE-6, CELL_SIZE-6); } if (key in pathState.closedNodes) { ctx.fillStyle = CLOSED_COLOR+"60"; ctx.fillRect(cx+3, cy+3, CELL_SIZE-6, CELL_SIZE-6); } } } // Overlay final path if found. Always draw after. if (pathState.path && pathState.path.length > 0) { ctx.save(); ctx.globalAlpha = 0.95; for (let i=0; i<pathState.path.length; ++i) { let [x, y] = pathState.path[i]; let cx = x*CELL_SIZE; let cy = y*CELL_SIZE; ctx.fillStyle = PATH_COLOR; ctx.beginPath(); ctx.arc(cx+CELL_SIZE/2, cy+CELL_SIZE/2, CELL_SIZE*0.23, 0, 2*Math.PI); ctx.fill(); } ctx.restore(); } // Draw start/goal: ctx.save(); // Start let [sx, sy] = start; let cx = sx*CELL_SIZE; let cy = sy*CELL_SIZE; ctx.fillStyle = START_COLOR; ctx.beginPath(); ctx.arc(cx+CELL_SIZE/2, cy+CELL_SIZE/2, CELL_SIZE*0.36, 0, 2*Math.PI); ctx.shadowColor = START_COLOR; ctx.shadowBlur = 13; ctx.fill(); ctx.shadowBlur = 0; // Goal let [gx, gy] = goal; let gcx = gx*CELL_SIZE; let gcy = gy*CELL_SIZE; ctx.fillStyle = GOAL_COLOR; ctx.beginPath(); ctx.arc(gcx+CELL_SIZE/2, gcy+CELL_SIZE/2, CELL_SIZE*0.36, 0, 2*Math.PI); ctx.shadowColor = GOAL_COLOR; ctx.shadowBlur = 13; ctx.fill(); ctx.restore(); // Maze walls (as lines) ctx.save(); ctx.strokeStyle = "#313347"; ctx.lineWidth = 2.1; for (let y=0; y<n; ++y) { for (let x=0; x<n; ++x) { let c = mazeObj[y][x]; let sx = x*CELL_SIZE, sy = y*CELL_SIZE; if (c.walls[0]) { // up ctx.beginPath(); ctx.moveTo(sx,sy); ctx.lineTo(sx+CELL_SIZE, sy); ctx.stroke(); } if (c.walls[1]) { // right ctx.beginPath(); ctx.moveTo(sx+CELL_SIZE, sy); ctx.lineTo(sx+CELL_SIZE, sy+CELL_SIZE); ctx.stroke(); } if (c.walls[2]) { // down ctx.beginPath(); ctx.moveTo(sx, sy+CELL_SIZE); ctx.lineTo(sx+CELL_SIZE, sy+CELL_SIZE); ctx.stroke(); } if (c.walls[3]) { // left ctx.beginPath(); ctx.moveTo(sx, sy); ctx.lineTo(sx, sy+CELL_SIZE); ctx.stroke(); } } } ctx.restore(); } // --------- Run and Animate -------- async function visualize(canvasId, timeId, algoGenFunc, mazeObj, grid, algoName, themeColor) { const canvas = document.getElementById(canvasId); const ctx = canvas.getContext("2d"); const n = grid.length; const start = [0, 0], goal = [n-1, n-1]; let gen = algoGenFunc(grid, start, goal); let done = false; let pathState = { openNodes: {}, closedNodes: {}, cameFrom: {}, path: [] }; let t0 = performance.now(); let iterationCount = 0; function doFrame() { let r = gen.next(); iterationCount++; if (!r.done) { pathState = r.value; } else { pathState = r.value; done = true; let t1 = performance.now(); document.getElementById(timeId).textContent = `Time: ${(t1-t0).toFixed(1)} ms | steps: ${iterationCount}`; } drawMaze(ctx, mazeObj, grid, pathState, start, goal); if (!done) setTimeout(doFrame, ANIMATION_SPEED); else { // Draw one more for "guaranteed path" setTimeout(() => drawMaze(ctx, mazeObj, grid, pathState, start, goal),100); } } document.getElementById(timeId).textContent = "Solving..."; doFrame(); } // ---- All Together --- function generateAndRun() { // Two different mazes let {maze: mazeA, grid: gridA} = generateMaze(MAZE_SIZE); let {maze: mazeB, grid: gridB} = generateMaze(MAZE_SIZE); visualize("canvasAStar", "timeAStar", astar, mazeA, gridA, "A*", OPEN_COLOR); visualize("canvasDijkstra", "timeDijkstra", dijkstra, mazeB, gridB, "Dijkstra", CLOSED_COLOR); } document.getElementById("regenerate").addEventListener("click", generateAndRun); // Start on first load generateAndRun(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html> <head> <title>Maze Generator and Pathfinding Visualizer</title> <style> body { display: flex; flex-direction: column; align-items: center; margin: 0; font-family: sans-serif; } canvas { border: 2px solid #333; } #footer { display: flex; justify-content: space-around; width: 100%; margin-top: 10px; font-size: 18px; } </style> </head> <body> <canvas id="mazeCanvas" width="1000" height="400"></canvas> <div id="footer"> <div>Dijkstra's Time: <span id="dijkstraTime">--</span> ms</div> <div>A* Time: <span id="astarTime">--</span> ms</div> </div> <script> const canvas = document.getElementById('mazeCanvas'); const ctx = canvas.getContext('2d'); const mazeSize = 10; const cellSize = 40; const mazeCount = 2; const start = { x: 0, y: 0 }; const end = { x: 9, y: 9 }; class Cell { constructor(x, y) { this.x = x; this.y = y; this.walls = { top: true, right: true, bottom: true, left: true }; this.visited = false; } } function generateMaze() { const maze = Array.from({length: mazeSize}, () => Array.from({length: mazeSize}, (_, x) => new Cell(x, y)) ); for (let y = 0; y < mazeSize; y++) { for (let x = 0; x < mazeSize; x++) { maze[y][x] = new Cell(x, y); } } const stack = []; const current = maze[0][0]; current.visited = true; stack.push(current); while (stack.length > 0) { const current = stack[stack.length - 1]; const neighbors = []; const checks = [ { dx: 0, dy: -1, wall: 'top', neighborWall: 'bottom' }, { dx: 1, dy: 0, wall: 'right', neighborWall: 'left' }, { dx: 0, dy: 1, wall: 'bottom', neighborWall: 'top' }, { dx: -1, dy: 0, wall: 'left', neighborWall: 'right' } ]; for (const dir of checks) { const nx = current.x + dir.dx, ny = current.y + dir.dy; if (nx >= 0 && ny >= 0 && nx < mazeSize && ny < mazeSize) { const neighbor = maze[ny][nx]; if (!neighbor.visited) { neighbors.push({ cell: neighbor, ...dir }); } } } if (neighbors.length === 0) { stack.pop(); continue; } const neighbor = neighbors[Math.floor(Math.random() * neighbors.length)]; current.walls[neighbor.wall] = false; neighbor.cell.walls[neighbor.neighborWall] = false; neighbor.cell.visited = true; stack.push(neighbor.cell); } return maze; } function isValidMove(fromX, fromY, toX, toY, maze) { if (toX < 0 || toY < 0 || toX >= mazeSize || toY >= mazeSize) return false; const cell = maze[fromY][fromX]; const dx = toX - fromX; const dy = toY - fromY; if (dx === 0) { if (dy === -1) return !cell.walls.top; if (dy === 1) return !cell.walls.bottom; } else if (dy === 0 && dx === 1) { return !cell.walls.right; } else if (dx === -1) { return !cell.walls.left; } return false; } function runDijkstra(maze, start, end, animationSteps) { const openList = []; const closedList = new Set(); const gMap = Array(mazeSize).fill().map(() => Array(mazeSize).fill(Infinity)); const cameFrom = {}; gMap[start.y][start.x] = 0; openList.push({ ...start, cost: 0 }); let steps = 0; const animate = () => { const canvasBackups = []; for (let i = 0; i < mazeCount; i++) { canvasBackups.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); } while (openList.length > 0) { openList.sort((a, b) => a.cost - b.cost); const current = openList.shift(); const key = `${current.x},${current.y}`; if (closedList.has(key)) continue; closedList.add(key); steps++; // Process neighbors const directions = [ { dx: 0, dy: -1 }, { dx: 1, dy: 0 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 } ]; for (const dir of directions) { const nx = current.x + dir.dx; const ny = current.y + dir.dy; if (!isValidMove(current.x, current.y, nx, ny, maze)) continue; const newCost = gMap[current.y][current.x] + 1; if (newCost < gMap[ny][nx]) { gMap[ny][nx] = newCost; cameFrom[`${nx},${ny}`] = { x: current.x, y: current.y }; openList.push({ x: nx, y: ny, cost: newCost }); } } animationSteps.push({ closed: [...closedList], open: [...openList], current, cameFrom: { ...cameFrom }, canvasBackups, algorithm: 'D' }); } for (let i = 0; i < mazeCount; i++) { ctx.putImageData(canvasBackups[i], 0, 0); } return reconstructPath(cameFrom, start, end); }; return animate(); } function runAStar(maze, start, end, animationSteps) { const openList = []; const closedList = new Set(); const gMap = Array(mazeSize).fill().map(() => Array(mazeSize).fill(Infinity)); const cameFrom = {}; const heuristic = (x, y) => Math.max(mazeSize - x, mazeSize - y); const hStart = heuristic(start.x, start.y); gMap[start.y][start.x] = 0; openList.push({ ...start, cost: hStart }); let steps = 0; const animate = () => { const canvasBackups = []; for (let i = 0; i < mazeCount; i++) { canvasBackups.push(ctx.getImageData(0, 0, canvas.width, canvas.height)); } while (openList.length > 0) { openList.sort((a, b) => a.cost - b.cost); const current = openList.shift(); const key = `${current.x},${current.y}`; if (closedList.has(key)) continue; closedList.add(key); steps++; const currentKey = `${current.x},${current.y}`; if (current.x === end.x && current.y === end.y) { break; } const directions = [ { dx: 0, dy: -1 }, { dx: 1, dy: 0 }, { dx: 0, dy: 1 }, { dx: -1, dy: 0 } ]; for (const dir of directions) { const nx = current.x + dir.dx; const ny = current.y + dir.dy; if (!isValidMove(current.x, current.y, nx, ny, maze)) continue; const newCost = gMap[current.y][current.x] + 1 + heuristic(nx, ny); if (gMap[current.y][current.x] + 1 < gMap[ny][nx]) { gMap[ny][nx] = gMap[current.y][current.x] + 1; cameFrom[`${nx},${ny}`] = { x: current.x, y: current.y }; openList.push({ x: nx, y: ny, cost: newCost }); } } animationSteps.push({ closed: [...closedList], open: [...openList], current, cameFrom: { ...cameFrom }, canvasBackups, algorithm: 'A' }); } for (let i = 0; i < mazeCount; i++) { ctx.putImageData(canvasBackups[i], 0, 0); } return reconstructPath(cameFrom, start, end); }; return animate(); } function reconstructPath(cameFrom, start, end) { const path = []; let key = `${end.x},${end.y}`; while (key && cameFrom[key]) { path.unshift(cameFrom[key]); key = `${cameFrom[key].x},${cameFrom[key].y}`; } return path; } function drawMaze(maze, offset) { const scale = cellSize / 20; ctx.strokeStyle = '#222'; ctx.lineWidth = 2 * scale; for (let y = 0; y < mazeSize; y++) { for (let x = 0; x < mazeSize; x++) { const cell = maze[y][x]; const drawX = offset + x * cellSize; const drawY = y * cellSize; if (cell.walls.top) { ctx.beginPath(); ctx.moveTo(drawX, drawY); ctx.lineTo(drawX + cellSize, drawY); ctx.stroke(); } if (cell.walls.right) { ctx.beginPath(); ctx.moveTo(drawX + cellSize, drawY); ctx.lineTo(drawX + cellSize, drawY + cellSize); ctx.stroke(); } if (cell.walls.bottom) { ctx.beginPath(); ctx.moveTo(drawX, drawY + cellSize); ctx.lineTo(drawX + cellSize, drawY + cellSize); ctx.stroke(); } if (cell.walls.left) { ctx.beginPath(); ctx.moveTo(drawX, drawY); ctx.lineTo(drawX, drawY + cellSize); ctx.stroke(); } } } } function drawNode(x, y, color, offset) { ctx.fillStyle = color; ctx.beginPath(); ctx.fillRect(offset + x * cellSize + 2, y * cellSize + 2, cellSize - 4, cellSize - 4); } function drawStartEnd(offset) { drawNode(start.x, start.y, '#2ecc71', offset); drawNode(end.x, end.y, '#e74c3c', offset); } function animate(animations, dijkstraPath, astarPath, times) { if (!animations.length) { const dijkstraPathEl = document.getElementById('dijkstraTime'); const astarTimeEl = document.getElementById('astarTime'); dijkstraPathEl.textContent = times[0].toFixed(2); astarTimeEl.textContent = times[1].toFixed(2); // Draw final paths function drawPath(path, offset, color) { if (!path || !path.length) return; ctx.strokeStyle = color; ctx.lineWidth = 5; ctx.beginPath(); for (let i = 0; i < path.length - 1; i++) { const midX = offset + (path[i].x + 0.5) * cellSize; const midY = (path[i].y + 0.5) * cellSize; if (i === 0) ctx.moveTo(midX, midY); ctx.lineTo(offset + (path[i+1].x + 0.5) * cellSize, (path[i+1].y + 0.5) * cellSize); } ctx.stroke(); } drawPath(dijkstraPath, 0, '#f39c12'); drawPath(astarPath, canvas.width/2, '#2980b9'); return; } const { closed, open, current, cameFrom, canvasBackups, algorithm } = animations.shift(); const isDijkstra = algorithm === 'D'; const offset = isDijkstra ? 0 : canvas.width / 2; // Restore previous state ctx.putImageData(canvasBackups[isDijkstra ? 0 : 1], 0, 0); // Draw explored nodes closed.forEach(key => { const [x, y] = key.split(',').map(Number); drawNode(x, y, isDijkstra ? '#f1c40f' : '#87ceeb', offset); }); open.forEach(node => { drawNode(node.x, node.y, isDijkstra ? '#e67e22' : '#48c9b0', offset); }); // Draw current node if (current) { drawNode(current.x, current.y, '#000', offset); } // Redraw start and end drawStartEnd(offset); requestAnimationFrame(() => animate(animations, dijkstraPath, astarPath, times)); } function renderAllMazes(mazes) { ctx.clearRect(0, 0, canvas.width, canvas.height); drawMaze(mazes[0], 0); drawMaze(mazes[1], canvas.width / 2); drawStartEnd(0); drawStartEnd(canvas.width / 2); } async function initializeAndRun() { const mazes = [generateMaze(), generateMaze()]; renderAllMazes(mazes); const animations = []; const times = []; const dijkstraStartTime = performance.now(); const dijkstraPath = runDijkstra(mazes[0], start, end, animations); const dijkstraEndTime = performance.now(); times[0] = dijkstraEndTime - dijkstraStartTime; const astarStartTime = performance.now(); const astarPath =
Drag to resize
### Maze Generator and Pathfinding Visualizer Below is a simple implementation of a maze generator and pathfinding visualizer using HTML5 Canvas and JavaScript. This code generates two 10x10 mazes, visualizes A\* and Dijkstra's algorithm solving them, and displays the time taken by each algorithm. ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Maze Generator and Pathfinding Visualizer</title> <style> body { font-family: Arial, sans-serif; } canvas { border: 1px solid black; } .container { display: flex; justify-content: space-around; } .maze-container { text-align: center; } </style> </head> <body> <div class="container"> <div class="maze-container"> <canvas id="maze-canvas-1" width="400" height="400"></canvas> <p id="time-taken-1"></p> </div> <div class="maze-container"> <canvas id="maze-canvas-2" width="400" height="400"></canvas> <p id="time-taken-2"></p> </div> </div> <script> // Define the maze size const mazeSize = 10; // Define the canvas elements const canvas1 = document.getElementById('maze-canvas-1'); const ctx1 = canvas1.getContext('2d'); const canvas2 = document.getElementById('maze-canvas-2'); const ctx2 = canvas2.getContext('2d'); // Define the time taken elements const timeTaken1 = document.getElementById('time-taken-1'); const timeTaken2 = document.getElementById('time-taken-2'); // Generate a random maze function generateMaze(size) { const maze = Array(size).fill().map(() => Array(size).fill(0)); for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { if (Math.random() < 0.2) { maze[i][j] = 1; // 1 represents a wall } } } maze[0][0] = 0; // Ensure the start cell is not a wall maze[size - 1][size - 1] = 0; // Ensure the end cell is not a wall return maze; } // Draw the maze on the canvas function drawMaze(ctx, maze) { const cellSize = canvas1.width / mazeSize; ctx.clearRect(0, 0, canvas1.width, canvas1.height); for (let i = 0; i < mazeSize; i++) { for (let j = 0; j < mazeSize; j++) { if (maze[i][j] === 1) { ctx.fillStyle = 'black'; ctx.fillRect(j * cellSize, i * cellSize, cellSize, cellSize); } } } } // A* algorithm function aStar(maze, ctx) { const startTime = performance.now(); const size = maze.length; const openList = [{ x: 0, y: 0, g: 0, h: Math.abs(size - 1) + Math.abs(size - 1), f: Math.abs(size - 1) + Math.abs(size - 1) }]; const closedList = Array(size).fill().map(() => Array(size).fill(false)); const cameFrom = Array(size).fill().map(() => Array(size).fill(null)); const cellSize = canvas1.width / size; function heuristic(a, b) { return Math.abs(a.x - b.x) + Math.abs(a.y - b.y); } function reconstructPath(current) { const path = []; while (current) { path.push(current); current = cameFrom[current.y][current.x]; } return path.reverse(); } async function search() { while (openList.length > 0) { openList.sort((a, b) => a.f - b.f); const current = openList.shift(); if (current.x === size - 1 && current.y === size - 1) { const path = reconstructPath(current); for (let i = 0; i < path.length; i++) { ctx.fillStyle = 'green'; ctx.fillRect(path[i].x * cellSize, path[i].y * cellSize, cellSize, cellSize); await new Promise(resolve => setTimeout(resolve, 50)); } const endTime = performance.now(); return endTime - startTime; } closedList[current.y][current.x] = true; ctx.fillStyle = 'red'; ctx.fillRect(current.x * cellSize, current.y * cellSize, cellSize, cellSize); await new Promise(resolve => setTimeout(resolve, 50)); const neighbors = [ { x: current.x - 1, y: current.y }, { x: current.x + 1, y: current.y }, { x: current.x, y: current.y - 1 }, { x: current.x, y: current.y + 1 } ]; for (const neighbor of neighbors) { if (neighbor.x >= 0 && neighbor.x < size && neighbor.y >= 0 && neighbor.y < size && !closedList[neighbor.y][neighbor.x] && maze[neighbor.y][neighbor.x] === 0) { const g = current.g + 1; const h = heuristic(neighbor, { x: size - 1, y: size - 1 }); const f = g + h; openList.push({ x: neighbor.x, y: neighbor.y, g, h, f }); cameFrom[neighbor.y][neighbor.x] = current; } } } return null; } return search(); } // Dijkstra's algorithm function dijkstra(maze, ctx) { const startTime = performance.now(); const size = maze.length; const openList = [{ x: 0, y: 0, g: 0 }]; const closedList = Array(size).fill().map(() => Array(size).fill(false)); const cameFrom = Array(size).fill().map(() => Array(size).fill(null)); const cellSize = canvas1.width / size; function reconstructPath(current) { const path = []; while (current) { path.push(current); current = cameFrom[current.y][current.x]; } return path.reverse(); } async function search() { while (openList.length > 0) { openList.sort((a, b) => a.g - b.g); const current = openList.shift(); if (current.x === size - 1 && current.y === size - 1) { const path = reconstructPath(current); for (let i = 0; i < path.length; i++) { ctx.fillStyle = 'green'; ctx.fillRect(path[i].x * cellSize, path[i].y * cellSize, cellSize, cellSize); await new Promise(resolve => setTimeout(resolve, 50)); } const endTime = performance.now(); return endTime - startTime; } closedList[current.y][current.x] = true; ctx.fillStyle = 'red'; ctx.fillRect(current.x * cellSize, current.y * cellSize, cellSize, cellSize); await new Promise(resolve => setTimeout(resolve, 50)); const neighbors = [ { x: current.x - 1, y: current.y }, { x: current.x + 1, y: current.y }, { x: current.x, y: current.y - 1 }, { x: current.x, y: current.y + 1 } ]; for (const neighbor of neighbors) { if (neighbor.x >= 0 && neighbor.x < size && neighbor.y >= 0 && neighbor.y < size && !closedList[neighbor.y][neighbor.x] && maze[neighbor.y][neighbor.x] === 0) { const g = current.g + 1; openList.push({ x: neighbor.x, y: neighbor.y, g }); cameFrom[neighbor.y][neighbor.x] = current; } } } return null; } return search(); } // Generate and solve two mazes async function main() { const maze1 = generateMaze(mazeSize); const maze2 = generateMaze(mazeSize); drawMaze(ctx1, maze1); drawMaze(ctx2, maze2); const time1 = await aStar(maze1, ctx1); const time2 = await dijkstra(maze2, ctx2); timeTaken1.textContent = `A* algorithm took ${time1.toFixed(2)}ms`; timeTaken2.textContent = `Dijkstra's algorithm took ${time2.toFixed(2)}ms`; } main(); </script> </body> </html> ``` This code first generates two random 10x10 mazes and draws them on two separate canvases. It then uses A\* algorithm to solve the first maze and Dijkstra's algorithm to solve the second maze, visualizing the process step by step. The time taken by each algorithm is displayed below the corresponding maze. The A\* and Dijkstra's algorithms are implemented using a priority queue (openList) to keep track of the cells to be explored. The algorithms iteratively explore the cells with the lowest cost (or heuristic cost in the case of A\*) until they reach the destination cell. The path is then reconstructed and visualized. The `main` function generates the mazes, solves them using the two algorithms, and displays the time taken by each algorithm. The `generateMaze` function generates a random maze, and the `drawMaze` function draws the maze on the canvas. The `aStar` and `dijkstra` functions implement the A\* and Dijkstra's algorithms, respectively.
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Maze Pathfinding Visualizer</title> <style> body { margin: 0; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); font-family: 'Arial', sans-serif; color: white; min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; text-align: center; } h1 { font-size: 2.5rem; margin-bottom: 30px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); } .maze-container { display: flex; justify-content: space-around; gap: 30px; margin: 30px 0; } .maze-section { background: rgba(255,255,255,0.1); border-radius: 15px; padding: 20px; backdrop-filter: blur(10px); box-shadow: 0 8px 32px rgba(0,0,0,0.2); } .algorithm-title { font-size: 1.5rem; margin-bottom: 15px; color: #ffd700; text-shadow: 1px 1px 2px rgba(0,0,0,0.5); } canvas { border: 3px solid rgba(255,255,255,0.3); border-radius: 10px; background: #2c3e50; box-shadow: 0 4px 16px rgba(0,0,0,0.3); } .stats { margin-top: 15px; font-size: 1.1rem; font-weight: bold; } .time-display { color: #00ff88; text-shadow: 0 0 5px rgba(0,255,136,0.5); } .controls { margin: 30px 0; } button { background: linear-gradient(45deg, #ff6b6b, #ff8e8e); border: none; color: white; padding: 15px 30px; font-size: 1.1rem; border-radius: 25px; cursor: pointer; margin: 0 10px; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255,107,107,0.3); } button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255,107,107,0.4); } .legend { display: flex; justify-content: center; gap: 20px; margin: 20px 0; flex-wrap: wrap; } .legend-item { display: flex; align-items: center; gap: 8px; background: rgba(255,255,255,0.1); padding: 8px 15px; border-radius: 15px; backdrop-filter: blur(10px); } .legend-color { width: 20px; height: 20px; border-radius: 3px; border: 1px solid rgba(255,255,255,0.3); } </style> </head> <body> <div class="container"> <h1>🔍 Maze Pathfinding Visualizer</h1> <div class="legend"> <div class="legend-item"> <div class="legend-color" style="background: #34495e;"></div> <span>Wall</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #ecf0f1;"></div> <span>Path</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #2ecc71;"></div> <span>Start</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #e74c3c;"></div> <span>End</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #3498db;"></div> <span>Exploring</span> </div> <div class="legend-item"> <div class="legend-color" style="background: #f39c12;"></div> <span>Final Path</span> </div> </div> <div class="maze-container"> <div class="maze-section"> <div class="algorithm-title">A* Algorithm</div> <canvas id="astarCanvas" width="400" height="400"></canvas> <div class="stats"> <div>Steps: <span id="astarSteps">0</span></div> <div class="time-display">Time: <span id="astarTime">0</span>ms</div> </div> </div> <div class="maze-section"> <div class="algorithm-title">Dijkstra's Algorithm</div> <canvas id="dijkstraCanvas" width="400" height="400"></canvas> <div class="stats"> <div>Steps: <span id="dijkstraSteps">0</span></div> <div class="time-display">Time: <span id="dijkstraTime">0</span>ms</div> </div> </div> </div> <div class="controls"> <button onclick="generateAndSolve()">🎲 Generate New Mazes</button> <button onclick="resetVisualization()">🔄 Reset</button> </div> </div> <script> const MAZE_SIZE = 10; const CELL_SIZE = 40; // Colors const COLORS = { WALL: '#34495e', PATH: '#ecf0f1', START: '#2ecc71', END: '#e74c3c', EXPLORING: '#3498db', FINAL_PATH: '#f39c12', VISITED: '#9b59b6' }; class MazeGenerator { constructor(size) { this.size = size; this.maze = []; this.generate(); } generate() { // Initialize maze with walls this.maze = Array(this.size).fill().map(() => Array(this.size).fill(1)); // Recursive backtracker algorithm const stack = []; const start = [1, 1]; this.maze[start[0]][start[1]] = 0; stack.push(start); const directions = [[0, 2], [2, 0], [0, -2], [-2, 0]]; while (stack.length > 0) { const current = stack[stack.length - 1]; const neighbors = []; for (const [dx, dy] of directions) { const nx = current[0] + dx; const ny = current[1] + dy; if (nx > 0 && nx < this.size - 1 && ny > 0 && ny < this.size - 1 && this.maze[nx][ny] === 1) { neighbors.push([nx, ny]); } } if (neighbors.length > 0) { const next = neighbors[Math.floor(Math.random() * neighbors.length)]; const wallX = current[0] + (next[0] - current[0]) / 2; const wallY = current[1] + (next[1] - current[1]) / 2; this.maze[next[0]][next[1]] = 0; this.maze[wallX][wallY] = 0; stack.push(next); } else { stack.pop(); } } // Ensure start and end are accessible this.maze[0][1] = 0; // Start this.maze[this.size - 1][this.size - 2] = 0; // End } } class PathfindingVisualizer { constructor(canvasId, algorithm) { this.canvas = document.getElementById(canvasId); this.ctx = this.canvas.getContext('2d'); this.algorithm = algorithm; this.maze = null; this.start = [0, 1]; this.end = [MAZE_SIZE - 1, MAZE_SIZE - 2]; this.isRunning = false; this.steps = 0; this.startTime = 0; } setMaze(maze) { this.maze = maze; this.drawMaze(); } drawMaze() { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); for (let i = 0; i < MAZE_SIZE; i++) { for (let j = 0; j < MAZE_SIZE; j++) { const x = j * CELL_SIZE; const y = i * CELL_SIZE; if (this.maze[i][j] === 1) { this.ctx.fillStyle = COLORS.WALL; } else { this.ctx.fillStyle = COLORS.PATH; } this.ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE); this.ctx.strokeStyle = '#2c3e50'; this.ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE); } } // Draw start and end this.drawCell(this.start[0], this.start[1], COLORS.START); this.drawCell(this.end[0], this.end[1], COLORS.END); } drawCell(row, col, color, alpha = 1) { this.ctx.save(); this.ctx.globalAlpha = alpha; this.ctx.fillStyle = color; this.ctx.fillRect(col * CELL_SIZE + 2, row * CELL_SIZE + 2, CELL_SIZE - 4, CELL_SIZE - 4); this.ctx.restore(); } async solve() { if (this.isRunning) return; this.isRunning = true; this.steps = 0; this.startTime = performance.now(); if (this.algorithm === 'astar') { await this.aStar(); } else { await this.dijkstra(); } const endTime = performance.now(); const duration = Math.round(endTime - this.startTime); document.getElementById(`${this.algorithm}Steps`).textContent = this.steps; document.getElementById(`${this.algorithm}Time`).textContent = duration; this.isRunning = false; } async aStar() { const openSet = [[...this.start, 0, this.heuristic(this.start)]]; const closedSet = new Set(); const cameFrom = new Map(); const gScore = new Map(); const fScore = new Map(); gScore.set(this.start.toString(), 0); fScore.set(this.start.toString(), this.heuristic(this.start)); while (openSet.length > 0) { openSet.sort((a, b) => a[3] - b[3]); const current = openSet.shift(); const [row, col] = current; if (row === this.end[0] && col === this.end[1]) { await this.drawFinalPath(cameFrom, [row, col]); return; } closedSet.add([row, col].toString()); this.drawCell(row, col, COLORS.EXPLORING, 0.7); this.steps++; await this.sleep(50); for (const [dr, dc] of [[0, 1], [1, 0], [0, -1], [-1, 0]]) { const newRow = row + dr; const newCol = col + dc; const neighbor = [newRow, newCol]; if (this.isValidCell(newRow, newCol) && !closedSet.has(neighbor.toString())) { const tentativeG = gScore.get([row, col].toString()) + 1; if (!gScore.has(neighbor.toString()) || tentativeG < gScore.get(neighbor.toString())) { cameFrom.set(neighbor.toString(), [row, col]); gScore.set(neighbor.toString(), tentativeG); const f = tentativeG + this.heuristic(neighbor); fScore.set(neighbor.toString(), f); if (!openSet.some(node => node[0] === newRow && node[1] === newCol)) { openSet.push([newRow, newCol, tentativeG, f]); } } } } } } async dijkstra() { const distances = new Map(); const previous = new Map(); const unvisited = []; for (let i = 0; i < MAZE_SIZE; i++) { for (let j = 0; j < MAZE_SIZE; j++) { if (this.maze[i][j] === 0) { distances.set([i, j].toString(), Infinity); unvisited.push([i, j]); } } } distances.set(this.start.toString(), 0); while (unvisited.length > 0) { unvisited.sort((a, b) => distances.get(a.toString()) - distances.get(b.toString()) ); const current = unvisited.shift(); const [row, col] = current; if (row === this.end[0] && col === this.end[1]) { await this.drawFinalPath(previous, current); return; } if (distances.get(current.toString()) === Infinity) break; this.drawCell(row, col, COLORS.EXPLORING, 0.7); this.steps++; await this.sleep(50); for (const [dr, dc] of [[0, 1], [1, 0], [0, -1], [-1, 0]]) { const newRow = row + dr; const newCol = col + dc; const neighbor = [newRow, newCol]; if (this.isValidCell(newRow, newCol)) { const alt = distances.get(current.toString()) + 1; if (alt < distances.get(neighbor.toString())) { distances.set(neighbor.toString(), alt); previous.set(neighbor.toString(), current); } } } } } async drawFinalPath(cameFrom, end) { const path = []; let current = end; while (cameFrom.has(current.toString())) { path.unshift(current); current = cameFrom.get(current.toString()); } for (const [row, col] of path) { if ((row !== this.start[0] || col !== this.start[1]) && (row !== this.end[0] || col !== this.end[1])) { this.drawCell(row, col, COLORS.FINAL_PATH); await this.sleep(50); } } } heuristic(pos) { return Math.abs(pos[0] - this.end[0]) + Math.abs(pos[1] - this.end[1]); } isValidCell(row, col) { return row >= 0 && row < MAZE_SIZE && col >= 0 && col < MAZE_SIZE && this.maze[row][col] === 0; } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } reset() { this.isRunning = false; this.steps = 0; document.getElementById(`${this.algorithm}Steps`).textContent = '0'; document.getElementById(`${this.algorithm}Time`).textContent = '0'; if (this.maze) { this.drawMaze(); } } } // Initialize visualizers const astarViz = new PathfindingVisualizer('astarCanvas', 'astar'); const dijkstraViz = new PathfindingVisualizer('dijkstraCanvas', 'dijkstra'); function generateAndSolve() { // Generate two different mazes const maze1 = new MazeGenerator(MAZE_SIZE); const maze2 = new MazeGenerator(MAZE_SIZE); astarViz.setMaze(maze1.maze); dijkstraViz.setMaze(maze2.maze); // Start solving both mazes simultaneously setTimeout(() => astarViz.solve(), 100); setTimeout(() => dijkstraViz.solve(), 100); } function resetVisualization() { astarViz.reset(); dijkstraViz.reset(); } // Generate initial mazes generateAndSolve(); </script> </body> </html> ```
Drag to resize
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Maze Generator and Pathfinding Visualizer</title> <style> body { font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; } .container { display: flex; gap: 20px; } .maze-container { border: 2px solid #333; border-radius: 10px; background-color: #fff; padding: 10px; text-align: center; } canvas { border: 1px solid #ccc; background-color: #e0e0e0; } .time-display { margin-top: 10px; font-size: 14px; color: #555; } </style> </head> <body> <div class="container"> <div class="maze-container"> <h3>Maze 1: A* Algorithm</h3> <canvas id="maze1" width="200" height="200"></canvas> <div id="timeAStar" class="time-display"></div> </div> <div class="maze-container"> <h3>Maze 2: Dijkstra's Algorithm</h3> <canvas id="maze2" width="200" height="200"></canvas> <div id="timeDijkstra" class="time-display"></div> </div> </div> <script> // Maze generation using recursive backtracking function generateMaze(width, height) { const maze = Array(height).fill().map(() => Array(width).fill(1)); // 1 for wall function carve(x, y) { const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; directions.sort(() => Math.random() * 2 - 1); // Shuffle maze[y][x] = 0; // 0 for path for (const [dx, dy] of directions) { const nx = x + dx * 2; const ny = y + dy * 2; if (nx >= 0 && nx < width && ny >= 0 && ny < height && maze[ny][nx] === 1) { maze[y + dy][x + dx] = 0; // Carve wall carve(nx, ny); } } } carve(1, 1); // Start from an odd point return maze; } // Utility for A* and Dijkstra function getNeighbors(maze, x, y) { const neighbors = []; const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; for (const [dx, dy] of directions) { const nx = x + dx; const ny = y + dy; if (nx >= 0 && nx < maze[0].length && ny >= 0 && ny < maze.length && maze[ny][nx] === 0) { neighbors.push([nx, ny]); } } return neighbors; } // Dijkstra's Algorithm function dijkstra(maze, start, end) { const distances = Array(maze.length).fill().map(() => Array(maze[0].length).fill(Infinity)); distances[start[1]][start[0]] = 0; const pq = [[0, start[0], start[1]]]; // [distance, x, y] const previous = Array(maze.length).fill().map(() => Array(maze[0].length).fill(null)); const visited = Array(maze.length).fill().map(() => Array(maze[0].length).fill(false)); while (pq.length > 0) { pq.sort((a, b) => a[0] - b[0]); // Sort by distance const [dist, x, y] = pq.shift(); if (visited[y][x]) continue; visited[y][x] = true; if (x === end[0] && y === end[1]) break; for (const [nx, ny] of getNeighbors(maze, x, y)) { const newDist = dist + 1; if (newDist < distances[ny][nx]) { distances[ny][nx] = newDist; previous[ny][nx] = [x, y]; pq.push([newDist, nx, ny]); } } } return { distances, previous }; } // A* Algorithm function aStar(maze, start, end) { const distances = Array(maze.length).fill().map(() => Array(maze[0].length).fill(Infinity)); distances[start[1]][start[0]] = 0; const fScores = Array(maze.length).fill().map(() => Array(maze[0].length).fill(Infinity)); fScores[start[1]][start[0]] = Math.abs(end[0] - start[0]) + Math.abs(end[1] - start[1]); // Manhattan heuristic const pq = [[fScores[start[1]][start[0]], 0, start[0], start[1]]]; // [fScore, gScore, x, y] const previous = Array(maze.length).fill().map(() => Array(maze[0].length).fill(null)); const visited = Array(maze.length).fill().map(() => Array(maze[0].length).fill(false)); while (pq.length > 0) { pq.sort((a, b) => a[0] - b[0]); // Sort by fScore const [, gScore, x, y] = pq.shift(); if (visited[y][x]) continue; visited[y][x] = true; if (x === end[0] && y === end[1]) break; for (const [nx, ny] of getNeighbors(maze, x, y)) { const tentativeGScore = gScore + 1; if (tentativeGScore < distances[ny][nx]) { previous[ny][nx] = [x, y]; distances[ny][nx] = tentativeGScore; fScores[ny][nx] = tentativeGScore + Math.abs(end[0] - nx) + Math.abs(end[1] - ny); pq.push([fScores[ny][nx], tentativeGScore, nx, ny]); } } } return { distances, previous }; } // Draw maze on canvas with animation function drawMaze(canvasId, maze, algorithm, start, end) { const canvas = document.getElementById(canvasId); const ctx = canvas.getContext('2d'); const cellSize = 20; // For 10x10 maze in 200x200 canvas const startTime = performance.now(); let result; if (canvasId === 'maze1') { result = aStar(maze, start, end); } else { result = dijkstra(maze, start, end); } const endTime = performance.now(); const timeTaken = (endTime - startTime).toFixed(2) + ' ms'; // Draw initial maze function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let y = 0; y < maze.length; y++) { for (let x = 0; x < maze[y].length; x++) { ctx.fillStyle = maze[y][x] === 1 ? '#333' : '#fff'; // Walls and paths ctx.fillRect(x * cellSize, y * cellSize, cellSize, cellSize); ctx.strokeStyle = '#ccc'; ctx.strokeRect(x * cellSize, y * cellSize, cellSize, cellSize); } } // Draw start and end ctx.fillStyle = 'green'; ctx.fillRect(start[0] * cellSize, start[1] * cellSize, cellSize, cellSize); ctx.fillStyle = 'red'; ctx.fillRect(end[0] * cellSize, end[1] * cellSize, cellSize, cellSize); } draw(); // Initial draw // Animate path function animatePath(previous, current) { if (!previous[current[1]][current[0]]) return; const path = []; let at = current; while (at) { path.push(at); at = previous[at[1]][at[0]]; } let index = 0; function step() { if (index < path.length) { const [px, py] = path[index]; ctx.fillStyle = 'blue'; // Path color ctx.fillRect(px * cellSize, py * cellSize, cellSize, cellSize); index++; setTimeout(step, 50); // Animation speed } else { // Display time if (canvasId === 'maze1') { document.getElementById('timeAStar').textContent = 'Time: ' + timeTaken; } else { document.getElementById('timeDijkstra').textContent = 'Time: ' + timeTaken; } } } step(); } const endPos = [maze[0].length - 2, maze.length - 2]; // End near bottom right animatePath(result.previous, endPos); } // Generate and visualize const maze1 = generateMaze(10, 10); const maze2 = generateMaze(10, 10); const start = [1, 1]; // Start position drawMaze('maze1', maze1, 'A*', start, [maze1[0].length - 2, maze1.length - 2]); drawMaze('maze2', maze2, 'Dijkstra', start, [maze2[0].length - 2, maze2.length - 2]); </script> </body> </html> ```
Drag to resize