【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>

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/64256.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux的shell脚本常用命令

1、前提 使用shell脚本可以将所要执行的命令行进行汇总&#xff0c;统一执行&#xff0c;制作为脚本工具&#xff0c;简化重复性工作 1.1、常用命令 1.1.1、启动命令 假设我们拥有一个halloWord.sh的脚本&#xff0c;通过cd 命令进入相对应的目录下 ./halloWord.sh1.1.2、…

Apipost接口自动化测试入门

今天我们来聊一聊接口自动化测试。以往我们都是以以代码的形式编写自动化测试脚本做自动化测试&#xff0c;网上也有非常多的攻略&#xff0c;那么在不会代码的情况下该怎么做接口自动化呢&#xff0c;今天给大家介绍Apipost自动化测试模块&#xff0c;不用写代码也能做接口自动…

字节C++后端面试总结

字节的面经,技术栈是 C++ 后端。 计算机网络 UDP和TCP区别 先说了概念一个是面向连接的基于字节流的可靠连接,一个是不需要连接的基于数据报的不可靠传输 然后说了几个小点,比如首部长度、应用场景、服务对象什么的。 补充: 还有一个很重要的点:UDP 的实时性比 TCP 好…

微服务技术栈(1.0)

微服务技术栈 认识微服务 单体架构 单体架构&#xff1a;将业务的所有功能集中在一个项目中开发&#xff0c;打成一个包部署 优点&#xff1a; 架构简单部署成本低 缺点&#xff1a; 耦合度高 分布式架构 分布式架构&#xff1a;根据业务功能对系统进行拆分&#xff0c…

鉴源实验室|公钥基础设施(PKI)在车联网中的应用

作者 | 付海涛 上海控安可信软件创新研究院汽车网络安全组 来源 | 鉴源实验室 01 PKI与车联网 1.1 PKI概述 公钥基础设施&#xff08;PKI ,Public Key Infrastructure&#xff09;是一种在现代数字环境中实现认证和加密的基本框架&#xff0c;主要用于保护网络交互和通信的安…

Mac电脑怎么使用“磁盘工具”修复磁盘

我们可以使用“磁盘工具”的“急救”功能来查找和修复磁盘错误。 “磁盘工具”可以查找和修复与 Mac 磁盘的格式及目录结构有关的错误。使用 Mac 时&#xff0c;错误可能会导致意外行为&#xff0c;而重大错误甚至可能会导致 Mac 彻底无法启动。 继续之前&#xff0c;请确保您…

Flutter父宽度自适应子控件的宽度

需求&#xff1a; 控件随着金币进行自适应宽度 image.png 步骤&#xff1a; 1、Container不设置宽度&#xff0c;需要设置约束padding&#xff1b; 2、文本使用Flexible形式&#xff1b; Container(height: 24.dp,padding: EdgeInsetsDirectional.only(start: 8.dp, end: 5.d…

LISA:通过大语言模型进行推理分割

论文&#xff1a;https://arxiv.org/pdf/2308.00692 代码&#xff1a;GitHub - dvlab-research/LISA 摘要 尽管感知系统近年来取得了显著的进步&#xff0c;但在执行视觉识别任务之前&#xff0c;它们仍然依赖于明确的人类指令来识别目标物体或类别。这样的系统缺乏主动推理…

跨境商城系统源码的优势,助力企业海外扩张

跨境电商发展背景与趋势 随着全球化的推进和互联网技术的快速发展&#xff0c;跨境电商已成为企业海外拓展的重要途径。然而&#xff0c;跨境电商面临着诸多挑战&#xff0c;如复杂的海外市场、文化差异、海关监管等。为了解决这些问题&#xff0c;企业可以借助跨境商城系统源码…

FOHEART H1数据手套:连接虚拟与现实,塑造智能交互新未来

在全新交互时代背景中&#xff0c;数据手套无疑是一种重要的科技产物。它不仅彻底改变了我们与虚拟世界的互动方式&#xff0c;更为我们提供了一种全新、更为直观的交互形式。 FOHEART H1数据手套结合了虚拟现实、手势识别等高新技术&#xff0c;用先进的传感技术和精准的数据…

TeeChart NET for MAUI Crack

TeeChart NET for MAUI Crack 跨平台图表-移动或桌面应用程序的核心图表代码相同。 图表集合-60多种图表类型和50多种财务和统计指标。 图表类型 60多种2D和3D图表类型以及多种组合&#xff0c;包括&#xff1a; 标准&#xff1a;线条(条形)、条形、区域、饼图、快线、点(散点…

LVS集群

目录 1、lvs简介&#xff1a; 2、lvs架构图&#xff1a; 3、 lvs的工作模式&#xff1a; 1&#xff09; VS/NAT&#xff1a; 即&#xff08;Virtual Server via Network Address Translation&#xff09; 2&#xff09;VS/TUN &#xff1a;即&#xff08;Virtual Server v…

三极管从入门到精通

文章目录 摘要1 基础1.1 PN结1.2 三极管 2 三极管模拟电路知识2.1 I-V特性曲线2.2 极限参数解释2.3 基本共射极放大电路2.4 小信号模型2.5 用小信号模型分析基本共射极放大电路 3 三极管实际模拟电路应用图3.1 共射极放大电路3.1.1 基本共射极放大电路3.1.2 基极分压式射极偏置…

实际工作中通过python+go-cqhttp+selenium实现自动检测维护升级并发送QQ通知消息(程序内测)

说明&#xff1a;该篇博客是博主一字一码编写的&#xff0c;实属不易&#xff0c;请尊重原创&#xff0c;谢谢大家&#xff01; 首先&#xff0c;今年比较忙没有多余时间去实操创作分享文章给大家&#xff0c;那就给大家分享下博主在实际工作中的一点点内容吧&#xff0c;就当交…

架构训练营学习笔记:5-1 计算架构模式之多级缓存架构

序 本节主要是计算架构。 多级缓存架构 缓存与缓冲&#xff1a;通常场景是读缓存&#xff0c;写缓冲。 缓存技术的本质&#xff1a;空间换时间&#xff0c;因此缓存架构属于高性能计算 架构。 缓存设计框架 主要考虑存什么&#xff1f;存多久&#xff1f;存哪里&#xff1f;如…

成品短视频App源码开发:一步步教你搭建短视频平台

近年来&#xff0c;短视频平台的兴起迅速改变了人们对视频内容的获取方式&#xff0c;成品短视频App源码的开发也因此备受瞩目。对于希望快速搭建短视频平台的创业者来说&#xff0c;使用成品短视频App源码是一个明智的选择。 成品短视频App源码为您提供了一个基于现有技术和功…

vue3+antd——实现个人中心页面+同步更改头部用户信息——基础积累

之前写过一篇文章关于vue3antd的框架模板&#xff0c;链接如下&#xff1a;http://t.csdn.cn/9dZMS 首先感谢大神提供的后台管理系统的模板&#xff0c;在此基础上改动要简单很多&#xff0c;主要是自己有很多内容不太敢随意改动。。。 直接看【个人中心】页面的效果图&#…

[迁移学习]领域泛化

一、概念 相较于领域适应&#xff0c;领域泛化(Domain generalization)最显著的区别在于训练过程中不能访问测试集。 领域泛化的损失函数一般可以描述为以下形式&#xff1a; 该式分为三项&#xff1a;第一项表示各训练集权重的线性组合&#xff0c;其中π为使该项最小的系数&a…

【Docker系列】push镜像报错问题解决方案

1 问题描述 docker push 报这个错&#xff0c;unknown blob 详细报错内容&#xff1a; Use docker scan to run Snyk tests against images to find vulnerabilities and learn how to fix them The push refers to repository [192.******/*******/*************] 3b3341e9d03…

网络安全进阶学习第十三课——SQL注入Bypass姿势

文章目录 一、等号被过滤二、substr、mid等被过滤三、逗号被过滤四、and/or被过滤五、空格被过滤五、其他绕过方式 一、等号被过滤 1、like&#xff0c;rlike语句&#xff0c;其中rlike是正则2、大于号>&#xff0c;小于号<3、符号<>&#xff1a;<>为不等于…