【js小游戏&案例】js迷宫二:当迷宫遇上算法

序幕:

在上次用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>

上述如何表达错误或者不明白的,可以评论留言一起讨论

猜你喜欢

转载自blog.csdn.net/weixin_52479803/article/details/132078539