序幕:
在上次用js开发固定迷宫时就想,这样的死迷宫不能称之为迷宫,如何让这个迷宫动起来呢?
让浏览器每次刷新时,通过计算重新生成一个迷宫,但这样有个问题,每次动态生成的迷宫必须保证它是通路的,通过思考以及借鉴,决定使用深度优先算法来实现
保证渲染迷宫为通路后,然后将其中一个固定为终点,随机初始化一点为起点,这样说可能比较难懂放上一张图,接着描述
实现:
首先定义一个二维数组,初始化全都为1,然后随机一个位置作为初始通路,设置为0,效果如下:
然后通过上下左右加减一,来判断下一步的四周边是什么情况(是通路还是墙壁)。如果加减一的下一步在二维数组范围内且位置是墙壁,我们就再来一次上下左右加减下一步,再次了解下下一步四周的情况,如果四周有且只有一条通路,就可以将这个点设置为通路,意思如下如:
因为是递归循环,如果碰到墙壁无法走通时,某一循环就结束,继续某一循环的上一级循环开始
代码大致如下:
// 深度优先搜索算法
function generatePath(maze, row, col) {
const directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]; // 上、右、下、左
// 解构赋值,遍历随机化后的方向数组
for (const [dx, dy] of directions) {
const newRow = row + dx; // 计算新位置的行号
const newCol = col + dy; // 计算新位置的列号
// 检查新位置是否在迷宫范围内且为墙壁
if (
newRow >= 0 && newRow < maze.length &&
newCol >= 0 && newCol < maze[0].length &&
maze[newRow][newCol] === 1
) {
// 检查新位置的上、下、左、右四个方向的邻居是否是通路
let count = 0; // 记录有多少个邻居是通路
for (const [dirX, dirY] of directions) {
const neighborRow = newRow + dirX; // 计算邻居位置的行号
const neighborCol = newCol + dirY; // 计算邻居位置的列号
// 检查邻居是否在迷宫范围内且为通路
if (
neighborRow >= 0 && neighborRow < maze.length &&
neighborCol >= 0 && neighborCol < maze[0].length &&
maze[neighborRow][neighborCol] === 0
) {
count++; // 如果是通路,计数加一
}
}
// 如果有且仅有一个邻居是通路,则将新位置设为通路
if (count === 1) {
// console.log(newRow,newCol)
maze[newRow][newCol] = 0; // 将新位置设为通路
generatePath(maze, newRow, newCol); // 递归调用生成路径,继续向新位置探索
}
}
}
}
但是directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]规定死的,这样生成的迷宫大致图形就相差不大,如果每次递归时,将这个数据打乱,效果就很棒了,如下代码:
// 洗牌算法
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>幼儿迷宫</title>
</head>
<style>
body {
padding: 0;
margin: 0;
}
table {
border-collapse: collapse;
padding: 0;
background: url("./gass.jpg");
height: 100%;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
}
td {
display: inline-block;
padding: 0;
width: 80px;
height: 80px;
}
tr {
display: block;
}
</style>
<body onload="init()" onkeypress="keypress(event)">
<table id="map">
</table>
<script>
var initPoint = null
// var mapPoint = null
function generateMaze(rows, cols) {
// 创建一个空的二维数组
const maze = [];
for (let i = 0; i < rows; i++) {
maze[i] = [];
for (let j = 0; j < cols; j++) {
maze[i][j] = 1; // 初始化为墙壁(1)
}
}
// 随机选择一个起始位置
const startRow = Math.floor(Math.random() * rows);
const startCol = Math.floor(Math.random() * cols);
maze[startRow][startCol] = 0; // 起始位置设为通路(0)
initPoint = [startRow, startCol]
// 使用递归函数来生成迷宫
generatePath(maze, startRow, startCol);
return maze;
}
// 深度优先搜索算法
function generatePath(maze, row, col) {
const directions = [[-1, 0], [0, 1], [1, 0], [0, -1]]; // 上、右、下、左
shuffleArray(directions)
// 解构赋值,遍历随机化后的方向数组
for (const [dx, dy] of directions) {
const newRow = row + dx; // 计算新位置的行号
const newCol = col + dy; // 计算新位置的列号
// 检查新位置是否在迷宫范围内且为墙壁
if (
newRow >= 0 && newRow < maze.length &&
newCol >= 0 && newCol < maze[0].length &&
maze[newRow][newCol] === 1
) {
// 检查新位置的上、下、左、右四个方向的邻居是否是通路
let count = 0; // 记录有多少个邻居是通路
for (const [dirX, dirY] of directions) {
const neighborRow = newRow + dirX; // 计算邻居位置的行号
const neighborCol = newCol + dirY; // 计算邻居位置的列号
// 检查邻居是否在迷宫范围内且为通路
if (
neighborRow >= 0 && neighborRow < maze.length &&
neighborCol >= 0 && neighborCol < maze[0].length &&
maze[neighborRow][neighborCol] === 0
) {
count++; // 如果是通路,计数加一
}
}
// 如果有且仅有一个邻居是通路,则将新位置设为通路
if (count === 1) {
// console.log(newRow,newCol)
maze[newRow][newCol] = 0; // 将新位置设为通路
generatePath(maze, newRow, newCol); // 递归调用生成路径,继续向新位置探索
}
}
}
}
// 洗牌算法
function shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
// 使用示例
const rows = 10;
const cols = 10;
const maze = generateMaze(rows, cols);
var empty = 0; //空地或草坪
var stone = 1; //石头的标记是1
var panda = 8; //熊猫的标记是9
var point = 6; //终点
var stepNumber = 0 //步数
var mapData = maze
var mapPoint = [9, 9]; //初始化终点的位置
var row = mapData.length; //地图的行
var column = mapData[0].length; //地图的列
function init() {
//二维数组里,去初始化熊猫的位置
mapData[initPoint[0]][initPoint[1]] = panda
mapData[mapPoint[0]][mapPoint[1]] = point
loadData(mapData);
}
/**
* 渲染地图
*/
function loadData(mapData) {
// 获取地图对象
var map = document.getElementById("map");
//渲染一行八列的数据
var mapHTML = "";
for (var i = 0; i < row; i++) {
mapHTML += "<tr>";
for (var j = 0; j < column; j++) {
if (mapData[i][j] == 0) {
mapHTML += "<td></td>";
} else if (mapData[i][j] == 1) {
mapHTML += '<td><img src="./qualityControl.png" style="height: 80px; height: 80px; border-radius: 50%;" ></td>';
} else if (mapData[i][j] == 8) {
mapHTML += '<td><img src="./location.png" id="panda" style="height: 80px; height: 80px; border-radius: 50%;position: relative;" ></td>';
} else if (mapData[i][j] == 6) {
mapHTML += '<td><img src="./endPoint.png" id="panda" style="height: 80px; height: 80px; border-radius: 50%;position: relative;" ></td>';
}
}
mapHTML += "</tr>";
}
map.innerHTML = mapHTML;
}
/**
* 障碍检测(可加多个障碍条件)
* @param yPoint
* @param xPoint
* @returns {boolean}
*/
function checkStone(yPoint, xPoint) {
if (yPoint < 0 || xPoint < 0 || yPoint >= mapData.length || xPoint >= mapData[0].length) {
return 2
} else if (mapData[yPoint][xPoint] == stone) {
return 1
}
}
// 移动方法
function move(keynum, Value) {
stepNumber++
var pandaPos = document.getElementById("panda")
if (119 == keynum) {
const result = Value + 80;
pandaPos.style.bottom = result + "px";
} else if (100 == keynum) {
const result = Value + 80;
pandaPos.style.left = result + "px";
} else if (115 == keynum) {
const result = Value - 80;
pandaPos.style.bottom = result + "px";
} else if (97 == keynum) {
const result = Value - 80;
pandaPos.style.left = result + "px";
}
}
/**
* 监听wasd按键事件:w(上) s(下) a(左) d(右)
* @param e
*/
var keypress = function keypress(e) {
var pandaPos = document.getElementById("panda")
var keynum = window.event ? e.keyCode : e.which;
if (119 == keynum) {
var point = initPoint;
if (point[0] < row) {
var xPoint = initPoint[1];
var yPoint = initPoint[0] - 1;
if (checkStone(yPoint, xPoint) == 1) {
console.log("碰撞到石头了,停止动作")
return
} else if (checkStone(yPoint, xPoint) == 2) {
console.log("超出边界了,停止动作")
return
}
initPoint = [yPoint, xPoint]
const style = window.getComputedStyle(pandaPos);
const ValueStr = style.bottom;
const Value = parseInt(ValueStr, 10);
move(119, Value)
console.log("向上")
} else {
console.log("超出地图范围了,停止动作")
}
} else if (97 == keynum) {
var point = initPoint;
if (point[1] > 0) {
var xPoint = initPoint[1] - 1;
var yPoint = initPoint[0];
if (checkStone(yPoint, xPoint) == 1) {
console.log("碰撞到石头了,停止动作")
return
} else if (checkStone(yPoint, xPoint) == 2) {
console.log("超出边界了,停止动作")
return
}
initPoint = [yPoint, xPoint]
const style = window.getComputedStyle(pandaPos);
const ValueStr = style.left;
const Value = parseInt(ValueStr, 10);
move(97, Value)
console.log("向左")
} else {
console.log("超出地图范围了,停止动作")
}
} else if (115 == keynum) {
var point = initPoint;
if (point[0] < row) {
var xPoint = initPoint[1];
var yPoint = initPoint[0] + 1;
if (checkStone(yPoint, xPoint) == 1) {
console.log("碰撞到石头了,停止动作")
return
} else if (checkStone(yPoint, xPoint) == 2) {
console.log("超出边界了,停止动作")
return
}
initPoint = [yPoint, xPoint]
const style = window.getComputedStyle(pandaPos);
const ValueStr = style.bottom;
const Value = parseInt(ValueStr, 10);
move(115, Value)
console.log("向下")
} else {
console.log("超出地图范围了,停止动作")
}
} else if (100 == keynum) {
var point = initPoint;
if (point[1] < column - 1) {
var xPoint = initPoint[1] + 1;
var yPoint = initPoint[0];
if (checkStone(yPoint, xPoint) == 1) {
console.log("碰撞到石头了,停止动作")
return
} else if (checkStone(yPoint, xPoint) == 2) {
console.log("超出边界了,停止动作")
return
}
initPoint = [yPoint, xPoint]
const style = window.getComputedStyle(pandaPos);
const ValueStr = style.left;
const Value = parseInt(ValueStr, 10);
move(100, Value)
console.log("向右")
} else {
console.log("超出地图范围了,停止动作")
}
}
if (initPoint[0] == 9 && initPoint[1] == 9) {
console.log()
alert(`总共${stepNumber},到达终点`);
location.reload();
}
}
</script>
</body>
</html>
上述如何表达错误或者不明白的,可以评论留言一起讨论