
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.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>
``````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>
``````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>
```
```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 =### 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.```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>
``````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>
```