迷宫生成与寻路可视化
1.美观的界面设计:
柔和的色彩搭配(淡蓝色、淡紫色、淡绿色等)
圆角边框和阴影效果
响应式布局适应不同屏幕尺寸
2.迷宫生成功能:
使用深度优先搜索(DFS)算法生成迷宫
可调整迷宫大小(10×10到40×40)
动画展示迷宫生成过程
3.寻路功能:
使用A*算法寻找最短路径
可视化探索过程和最终路径
显示路径长度统计
4.交互控制:
调整动画速度
单独生成迷宫或寻路
重置功能
5.详细的算法解释:
逐步说明迷宫生成和寻路算法
颜色图例说明
实时统计信息
6.使用说明:
选择迷宫大小和算法
点击"生成迷宫"按钮创建迷宫
点击"寻找路径"按钮查看最短路径
使用速度滑块调整动画速度
点击"重置"按钮重新开始
7.截图展示:
8.代码重现:
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>迷宫生成与寻路可视化</title><style>body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f5f7fa;color: #4a5568;margin: 0;padding: 20px;display: flex;flex-direction: column;align-items: center;min-height: 100vh;}h1 {color: #5b6abf;margin-bottom: 10px;text-shadow: 1px 1px 2px rgba(0,0,0,0.1);}.subtitle {color: #718096;margin-bottom: 30px;font-size: 1.1em;}.container {display: flex;flex-wrap: wrap;justify-content: center;gap: 30px;width: 100%;max-width: 1200px;}.maze-container {position: relative;background-color: #fff;border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);padding: 20px;width: 600px;height: 600px;display: flex;justify-content: center;align-items: center;}#maze {border: 2px solid #e2e8f0;background-color: #f8fafc;}.controls {background-color: #fff;border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);padding: 20px;width: 400px;display: flex;flex-direction: column;gap: 15px;}.control-group {display: flex;flex-direction: column;gap: 8px;}label {font-weight: 600;color: #5b6abf;}select, input, button {padding: 10px 15px;border: 1px solid #e2e8f0;border-radius: 6px;background-color: #f8fafc;font-size: 14px;transition: all 0.2s;}select:focus, input:focus {outline: none;border-color: #5b6abf;box-shadow: 0 0 0 2px rgba(91, 106, 191, 0.2);}button {background-color: #5b6abf;color: white;border: none;cursor: pointer;font-weight: 600;margin-top: 5px;}button:hover {background-color: #4a56a0;transform: translateY(-1px);}button:active {transform: translateY(0);}.explanation {background-color: #fff;border-radius: 10px;box-shadow: 0 4px 15px rgba(0, 0, 0, 0.08);padding: 20px;margin-top: 20px;width: 100%;max-width: 1000px;}.explanation h2 {color: #5b6abf;margin-top: 0;}.step {margin-bottom: 15px;padding: 15px;background-color: #f8fafc;border-radius: 6px;border-left: 4px solid #5b6abf;}.step h3 {margin-top: 0;color: #4a5568;}.legend {display: flex;flex-wrap: wrap;gap: 15px;margin-top: 15px;}.legend-item {display: flex;align-items: center;gap: 5px;font-size: 14px;}.legend-color {width: 20px;height: 20px;border-radius: 3px;}.speed-control {display: flex;align-items: center;gap: 10px;}.speed-control input {flex-grow: 1;}.stats {display: flex;justify-content: space-between;margin-top: 10px;font-size: 14px;color: #718096;}</style>
</head>
<body><h1>迷宫生成与寻路可视化</h1><div class="subtitle">探索算法如何创建迷宫并找到最短路径</div><div class="container"><div class="maze-container"><canvas id="maze" width="500" height="500"></canvas></div><div class="controls"><div class="control-group"><label for="maze-size">迷宫大小</label><select id="maze-size"><option value="10">10×10 (简单)</option><option value="20" selected>20×20 (中等)</option><option value="30">30×30 (困难)</option><option value="40">40×40 (专家)</option></select></div><div class="control-group"><label for="algorithm">生成算法</label><select id="algorithm"><option value="dfs">深度优先搜索 (DFS)</option><option value="prim">普里姆算法 (Prim's)</option><option value="kruskal">克鲁斯卡尔算法 (Kruskal's)</option></select></div><div class="control-group"><label for="pathfinding">寻路算法</label><select id="pathfinding"><option value="astar">A* 算法</option><option value="dijkstra">迪杰斯特拉算法</option><option value="bfs">广度优先搜索 (BFS)</option><option value="dfs">深度优先搜索 (DFS)</option></select></div><div class="control-group"><label for="speed">动画速度</label><div class="speed-control"><input type="range" id="speed" min="1" max="20" value="10"><span id="speed-value">中速</span></div></div><button id="generate-btn">生成迷宫</button><button id="solve-btn">寻找路径</button><button id="reset-btn">重置</button><div class="legend"><div class="legend-item"><div class="legend-color" style="background-color: #cbd5e0;"></div><span>墙壁</span></div><div class="legend-item"><div class="legend-color" style="background-color: #f8fafc;"></div><span>路径</span></div><div class="legend-item"><div class="legend-color" style="background-color: #68d391;"></div><span>起点</span></div><div class="legend-item"><div class="legend-color" style="background-color: #f56565;"></div><span>终点</span></div><div class="legend-item"><div class="legend-color" style="background-color: #90cdf4;"></div><span>探索中</span></div><div class="legend-item"><div class="legend-color" style="background-color: #9f7aea;"></div><span>最短路径</span></div></div><div class="stats"><div id="maze-stats">迷宫大小: 20×20</div><div id="path-stats">路径长度: -</div></div></div></div><div class="explanation"><h2>算法解释</h2><div class="step"><h3>1. 迷宫生成 (深度优先搜索 - DFS)</h3><p>深度优先搜索通过随机选择方向并"挖通"墙壁来创建迷宫。算法从起点开始,随机选择一个未访问的相邻单元格,移除它们之间的墙壁,并递归继续这个过程。当遇到死胡同时,算法会回溯到之前的分支点。这种方法生成的迷宫通常有较长的走廊和较少的交叉点。</p></div><div class="step"><h3>2. 寻路 (A* 算法)</h3><p>A* 算法是一种高效的寻路算法,结合了迪杰斯特拉算法的保证找到最短路径和贪心算法的高效性。它通过评估每个可能的路径的成本函数 f(n) = g(n) + h(n) 来选择下一步,其中 g(n) 是从起点到当前节点的实际成本,h(n) 是从当前节点到终点的启发式估计成本(通常使用曼哈顿距离)。</p></div><div class="step"><h3>3. 可视化说明</h3><p>蓝色单元格表示算法正在探索的节点,紫色路径表示找到的最短路径。动画速度可以调整以观察算法的详细步骤或快速查看结果。迷宫生成和路径查找都可以单独执行,方便观察每个算法的独立效果。</p></div></div><script>// 获取DOM元素const canvas = document.getElementById('maze');const ctx = canvas.getContext('2d');const mazeSizeSelect = document.getElementById('maze-size');const algorithmSelect = document.getElementById('algorithm');const pathfindingSelect = document.getElementById('pathfinding');const speedSlider = document.getElementById('speed');const speedValue = document.getElementById('speed-value');const generateBtn = document.getElementById('generate-btn');const solveBtn = document.getElementById('solve-btn');const resetBtn = document.getElementById('reset-btn');const mazeStats = document.getElementById('maze-stats');const pathStats = document.getElementById('path-stats');// 颜色定义const colors = {wall: '#cbd5e0',path: '#f8fafc',start: '#68d391',end: '#f56565',visited: '#90cdf4',current: '#4299e1',solution: '#9f7aea',backtrack: '#fbb6ce'};// 迷宫参数let size = 20;let cellSize = 25;let maze = [];let visited = [];let stack = [];let start = { x: 0, y: 0 };let end = { x: size - 1, y: size - 1 };let isGenerating = false;let isSolving = false;let animationSpeed = 100;let animationId = null;let pathLength = 0;// 初始化迷宫function initMaze() {size = parseInt(mazeSizeSelect.value);cellSize = Math.floor(500 / size);canvas.width = size * cellSize;canvas.height = size * cellSize;maze = [];visited = [];stack = [];// 初始化所有单元格为墙壁for (let y = 0; y < size; y++) {maze[y] = [];visited[y] = [];for (let x = 0; x < size; x++) {maze[y][x] = { top: true, right: true, bottom: true, left: true,visited: false};visited[y][x] = false;}}start = { x: 0, y: 0 };end = { x: size - 1, y: size - 1 };mazeStats.textContent = `迷宫大小: ${size}×${size}`;pathStats.textContent = `路径长度: -`;drawMaze();}// 绘制迷宫function drawMaze() {ctx.clearRect(0, 0, canvas.width, canvas.height);// 绘制背景ctx.fillStyle = colors.path;ctx.fillRect(0, 0, canvas.width, canvas.height);// 绘制墙壁ctx.fillStyle = colors.wall;for (let y = 0; y < size; y++) {for (let x = 0; x < size; x++) {const cell = maze[y][x];const px = x * cellSize;const py = y * cellSize;if (cell.top) {ctx.fillRect(px, py, cellSize, 1);}if (cell.right) {ctx.fillRect(px + cellSize - 1, py, 1, cellSize);}if (cell.bottom) {ctx.fillRect(px, py + cellSize - 1, cellSize, 1);}if (cell.left) {ctx.fillRect(px, py, 1, cellSize);}}}// 绘制起点和终点drawCell(start.x, start.y, colors.start);drawCell(end.x, end.y, colors.end);}// 绘制单个单元格function drawCell(x, y, color) {const px = x * cellSize;const py = y * cellSize;const padding = 2;ctx.fillStyle = color;ctx.fillRect(px + padding, py + padding, cellSize - 2 * padding, cellSize - 2 * padding);// 重新绘制墙壁,确保它们可见const cell = maze[y][x];ctx.fillStyle = colors.wall;if (cell.top) {ctx.fillRect(px, py, cellSize, 1);}if (cell.right) {ctx.fillRect(px + cellSize - 1, py, 1, cellSize);}if (cell.bottom) {ctx.fillRect(px, py + cellSize - 1, cellSize, 1);}if (cell.left) {ctx.fillRect(px, py, 1, cellSize);}}// 深度优先搜索生成迷宫function generateMazeDFS() {if (isGenerating) return;isGenerating = true;generateBtn.disabled = true;initMaze();stack = [{ x: start.x, y: start.y }];maze[start.y][start.x].visited = true;function step() {if (stack.length === 0) {isGenerating = false;generateBtn.disabled = false;cancelAnimationFrame(animationId);return;}const current = stack[stack.length - 1];const neighbors = getUnvisitedNeighbors(current.x, current.y);if (neighbors.length > 0) {const next = neighbors[Math.floor(Math.random() * neighbors.length)];removeWall(current, next);maze[next.y][next.x].visited = true;visited[next.y][next.x] = true;stack.push(next);// 绘制当前单元格drawCell(current.x, current.y, colors.current);drawCell(next.x, next.y, colors.visited);} else {stack.pop();drawCell(current.x, current.y, colors.backtrack);}// 重新绘制起点和终点drawCell(start.x, start.y, colors.start);drawCell(end.x, end.y, colors.end);animationId = requestAnimationFrame(() => {setTimeout(step, animationSpeed);});}step();}// 获取未访问的邻居function getUnvisitedNeighbors(x, y) {const neighbors = [];if (y > 0 && !maze[y - 1][x].visited) neighbors.push({ x, y: y - 1, dir: 'top' });if (x < size - 1 && !maze[y][x + 1].visited) neighbors.push({ x: x + 1, y, dir: 'right' });if (y < size - 1 && !maze[y + 1][x].visited) neighbors.push({ x, y: y + 1, dir: 'bottom' });if (x > 0 && !maze[y][x - 1].visited) neighbors.push({ x: x - 1, y, dir: 'left' });return neighbors;}// 移除两个单元格之间的墙function removeWall(current, next) {if (current.x === next.x) {if (current.y > next.y) {maze[current.y][current.x].top = false;maze[next.y][next.x].bottom = false;} else {maze[current.y][current.x].bottom = false;maze[next.y][next.x].top = false;}} else if (current.y === next.y) {if (current.x > next.x) {maze[current.y][current.x].left = false;maze[next.y][next.x].right = false;} else {maze[current.y][current.x].right = false;maze[next.y][next.x].left = false;}}}// A* 寻路算法function solveAStar() {if (isSolving || isGenerating) return;isSolving = true;solveBtn.disabled = true;// 重置访问状态for (let y = 0; y < size; y++) {for (let x = 0; x < size; x++) {visited[y][x] = false;}}// 初始化开放列表和关闭列表const openSet = [];const closedSet = [];const cameFrom = {};const gScore = {};const fScore = {};// 初始化所有节点的gScore和fScore为无穷大for (let y = 0; y < size; y++) {for (let x = 0; x < size; x++) {const key = `${x},${y}`;gScore[key] = Infinity;fScore[key] = Infinity;}}// 设置起点的分数const startKey = `${start.x},${start.y}`;gScore[startKey] = 0;fScore[startKey] = heuristic(start, end);openSet.push({ x: start.x, y: start.y });function step() {if (openSet.length === 0) {// 没有找到路径isSolving = false;solveBtn.disabled = false;cancelAnimationFrame(animationId);return;}// 找到fScore最小的节点let currentIndex = 0;for (let i = 1; i < openSet.length; i++) {const key1 = `${openSet[i].x},${openSet[i].y}`;const key2 = `${openSet[currentIndex].x},${openSet[currentIndex].y}`;if (fScore[key1] < fScore[key2]) {currentIndex = i;}}const current = openSet[currentIndex];const currentKey = `${current.x},${current.y}`;// 如果到达终点if (current.x === end.x && current.y === end.y) {reconstructPath(cameFrom, current);isSolving = false;solveBtn.disabled = false;cancelAnimationFrame(animationId);return;}// 从开放列表移动到关闭列表openSet.splice(currentIndex, 1);closedSet.push(current);visited[current.y][current.x] = true;// 检查所有邻居const neighbors = getPassableNeighbors(current.x, current.y);for (const neighbor of neighbors) {const neighborKey = `${neighbor.x},${neighbor.y}`;// 如果邻居已经在关闭列表中,跳过if (closedSet.some(cell => cell.x === neighbor.x && cell.y === neighbor.y)) {continue;}// 计算从起点到邻居的临时gScoreconst tentativeGScore = gScore[currentKey] + 1;// 如果邻居不在开放列表中,添加它if (!openSet.some(cell => cell.x === neighbor.x && cell.y === neighbor.y)) {openSet.push(neighbor);} else if (tentativeGScore >= gScore[neighborKey]) {// 这不是更好的路径continue;}// 这是目前最好的路径,记录下来cameFrom[neighborKey] = current;gScore[neighborKey] = tentativeGScore;fScore[neighborKey] = gScore[neighborKey] + heuristic(neighbor, end);}// 绘制探索过程drawMaze();for (const cell of openSet) {drawCell(cell.x, cell.y, colors.visited);}for (const cell of closedSet) {drawCell(cell.x, cell.y, colors.backtrack);}drawCell(current.x, current.y, colors.current);drawCell(start.x, start.y, colors.start);drawCell(end.x, end.y, colors.end);animationId = requestAnimationFrame(() => {setTimeout(step, animationSpeed);});}step();}// 获取可通过的邻居function getPassableNeighbors(x, y) {const neighbors = [];const cell = maze[y][x];if (!cell.top && y > 0) neighbors.push({ x, y: y - 1 });if (!cell.right && x < size - 1) neighbors.push({ x: x + 1, y });if (!cell.bottom && y < size - 1) neighbors.push({ x, y: y + 1 });if (!cell.left && x > 0) neighbors.push({ x: x - 1, y });return neighbors;}// 启发式函数 (曼哈顿距离)function heuristic(a, b) {return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);}// 重建路径function reconstructPath(cameFrom, current) {const path = [current];let currentKey = `${current.x},${current.y}`;while (cameFrom[currentKey]) {current = cameFrom[currentKey];path.unshift(current);currentKey = `${current.x},${current.y}`;}pathLength = path.length;pathStats.textContent = `路径长度: ${pathLength}`;// 绘制路径drawMaze();for (const cell of path) {drawCell(cell.x, cell.y, colors.solution);}drawCell(start.x, start.y, colors.start);drawCell(end.x, end.y, colors.end);}// 事件监听器generateBtn.addEventListener('click', generateMazeDFS);solveBtn.addEventListener('click', solveAStar);resetBtn.addEventListener('click', () => {cancelAnimationFrame(animationId);isGenerating = false;isSolving = false;generateBtn.disabled = false;solveBtn.disabled = false;initMaze();});speedSlider.addEventListener('input', () => {const speed = parseInt(speedSlider.value);animationSpeed = 105 - (speed * 5); // 从100ms到5ms// 更新速度标签if (speed < 5) {speedValue.textContent = '慢速';} else if (speed < 15) {speedValue.textContent = '中速';} else {speedValue.textContent = '快速';}});// 初始化initMaze();</script>
</body>
</html>