• 原生js实现扫雷


    最近对扫雷比较感兴趣 就打算模仿者写一下算法
    全程都是自己想到的 不一定是标准的 但是可以用
    效果图如下:
    在这里插入图片描述
    在这里插入图片描述
    实现过程以及原理:

    前置全局数据

    // 画布元素
    const canvasDom = document.querySelector("#canvas");
    // 基础信息
    const config = {
      dom: canvasDom,
      height: canvasDom.offsetHeight,
      width: canvasDom.offsetWidth,
      top: canvasDom.offsetTop,
      left: canvasDom.offsetLeft,
      right: 0,
      bottom: 0,
    };
    config.right = config.left + config.width;
    config.bottom = config.top + config.height;
    console.log("config -->>", config);
    // 网格大小
    const gridSize = {
      xSize: 20,
      YSize: 20,
      boomSize: 90,
      boomSizeBack: 0,
      nowBoomSize: 0
    };
    // 每个格子大小
    const gridInfo = {
      gridWidth: config.width / gridSize.xSize,
      gridHeight: config.height / gridSize.YSize,
    };
    let isEnd = false; // 是否已经结束
    let firstClick = true; // 是否第一次点击
    console.log("gridInfo -->>", gridInfo);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    1 生成网格信息

    实现思路:

    1.1:生成网格

    其中 xSize*YSize 则是最大格子数量,boomSize则是最大生成雷的数量,
    通过生成出来的数据可以包含以下信息:open是否被扫开isBoom是否属于雷offsetX,offsetY偏移量x,y当前雷的坐标width,height宽度。
    然后进行数据保存 我是通过对象的形式进行保存的 可以优化一些读取速度 具体形式如下:

    当中的key 是通过当前 x,y 坐标轴方式记录 `${x},${y}`如下
    '0,0': {
    	x: '',
        y: '',
        offsetX: '',
        offsetY: '',
        width: '',
        height: '',
        open: false,
        isBoom: false,
    },
    '0,1':{
    	...
    },
    '19,19' : {
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现代码如下:

    
    // 初始化表格数据
    function initData() {
      /**
       * gridSize: 网格大小
       * gridInfo: 单个网格信息
       */
      // 获取雷的生成数量
      gridSize.boomSizeBack = gridSize.boomSize;
      gridSize.nowBoomSize = gridSize.boomSize;
      // 简单判断是否超出容器
      if (gridSize.boomSizeBack >= gridSize.xSize * gridSize.YSize + 9) {
        throw "雷的数量超过格子总数";
      }
      for (let yIndex = 0; yIndex < gridSize.YSize; yIndex++) {
        for (let xIndex = 0; xIndex < gridSize.xSize; xIndex++) {
          const item = {
            x: xIndex,
            y: yIndex,
            offsetX: xIndex * gridInfo.gridWidth,
            offsetY: yIndex * gridInfo.gridHeight,
            width: gridInfo.gridWidth,
            height: gridInfo.gridHeight,
            open: false,
            isBoom: false,
          };
          gridStore.setId(`${xIndex},${yIndex}`, item);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    1.2 第一次开图

    我目前是 第一次点击无论如何都不能触发雷的思路所以实现思路如下:

    // 获取x、y轴
    function getEventPosition(ev) {
      let x, y;
      if (ev.layerX || ev.layerX == 0) {
        x = ev.layerX;
        y = ev.layerY;
      } else if (ev.offsetX || ev.offsetX == 0) {
        // Opera
        x = ev.offsetX;
        y = ev.offsetY;
      }
    
      return { x, y };
    }
    
    canvasDom.addEventListener(
        "mouseup",
        function (e) {
          // 获取点击坐标
          const p = getEventPosition(e);
          // 通过点击道德坐标获取相对应数据Id
          let x = p.x;
    	  let y = p.y;
    	  x = target.x - e.left;
    	  y = target.y - e.top;
    	  let xIndex = Math.ceil(x / gridInfo.gridWidth) - 1;
    	  let yIndex = Math.ceil(y / gridInfo.gridHeight) - 1;
          const target = gridStore.getId(`${xIndex},${yIndex}`);
          if (target) {
            if (!firstClick) {
            	firstClick = false;
              	// 如果是第一次点击 则进行打开地图 防止第一次点击就触发雷
              	openMap(xIndex, yIndex);
              	initBoom();
            }
          }
          // 刷新显示
          reloadGrid();
        },
        false
      );
    
    function openMap(x, y) {
      for (let xIndex = x - 1; xIndex < x + 2; xIndex++) {
        for (let yIndex = y - 1; yIndex < y + 2; yIndex++) {
          const id = `${xIndex},${yIndex}`;
          const target = gridStore.getId(id);
          if (target) {
            target.open = true;
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    其中openMap 获取到的坐标点 通过 x-1 x+2的方式 获取九宫格需要循环的x轴 y-1 y+2的方式获取九宫格需要循环的y轴 得到的则是 3x3的空间 将周围的数据标记为 open

    1.3 生成雷

    这里进行的方式是通过定义的雷的数量进行循环 随机获取数据中的下标
    其中 为了防止随机到的可能会出现相同的 则会进行递归调用

    // 初始化雷
    function initBoom() {
      const gridData = gridStore.getStore();
      const gridList = Object.keys(gridData);
      const boomList = [];
      let sameIndex = 0;
    
      for (let index = 0; index < gridSize.boomSizeBack; index++) {
      	// 随机取到下标 进行随机生成雷
        const targetIndex = Math.floor(Math.random() * gridList.length);
        const id = gridList[targetIndex];
        const item = gridStore.getId(id);
    
        if (!item.isBoom && !item.open) {
          item.isBoom = true;
          boomList.push(item);
          boomStore.setId(id, item);
        } else {
          // 如果出现随机到相同的下标 进行标记 后续重新随机生成
          sameIndex += 1;
        }
      }
    
      gridSize.boomSizeBack = sameIndex;
      // 如果包含相同的下标 则进行递归调用 保证雷生成出来的数量是和指定的相同
      if (sameIndex) {
        initBoom();
      } else {
      	// 调用自动开图
        autoOpenMap();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    1.4 第一次自动打开安全的区域

    其中 这一部分我想到的就是 暴力循环 还没有想到其他的快速计算的方式
    思路是 循环所有的数据 进行9宫格判断是否包含有雷 如果都没有雷 则进行打开

    // 自动打开地图
    function autoOpenMap() {
      const gridData = gridStore.getStore();
      for (const key in gridData) {
        const safeItem = gridData[key];
    
        isSafeMap(safeItem.x, safeItem.y);
    
        const safeNumber = getSafeNumber(safeItem.x, safeItem.y);
        safeItem.number = safeNumber;
      }
    }
    // 进行九宫格判断
    function isSafeMap(x, y) {
      const tempMap = {};
      let isSafe = true;
      safeFor: for (let xIndex = x - 1; xIndex < x + 2; xIndex++) {
        for (let yIndex = y - 1; yIndex < y + 2; yIndex++) {
          const id = `${xIndex},${yIndex}`;
          const target = gridStore.getId(id);
          if (target) {
            tempMap[id] = target;
            if (target.isBoom) {
              isSafe = false;
              break safeFor; // 整段跳出循环
            }
          }
        }
      }
    
      if (isSafe) {
        // 如果没有发现雷 则进行打开
        for (const key in tempMap) {
          const item = tempMap[key];
          item.open = true;
          safeStore.setId(key, item);
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    1.4 标记周围雷数量

    这个就是简单的循环进行数数字了 也算是暴力循环 待优化

    // 添加周围数字提醒
    function getSafeNumber(x, y) {
      let safeIndex = 0;
      for (let xIndex = x - 1; xIndex < x + 2; xIndex++) {
        for (let yIndex = y - 1; yIndex < y + 2; yIndex++) {
          const id = `${xIndex},${yIndex}`;
          const target = gridStore.getId(id);
          if (target && target.isBoom) {
            safeIndex += 1;
          }
        }
      }
      return safeIndex;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    1.5 标记后一键打开周围的地图

    当点击数字后进行判断周围的标记数量 如果标记数量和当前的数字一样 则进行打开其余为打开的区域 当然如果是标记错误的进行点击到雷处理

    
    // 打开标记周围的图
    function openMarkMap(target) {
      const x = target.x;
      const y = target.y;
      let index = 0;
      let targetNumber = target.number;
      const mapList = [];
      for (let xIndex = x - 1; xIndex < x + 2; xIndex++) {
        for (let yIndex = y - 1; yIndex < y + 2; yIndex++) {
          const id = `${xIndex},${yIndex}`;
          const target = gridStore.getId(id);
          if (target) {
            mapList.push(target);
            if (target.mark) {
              index += 1;
            }
          }
        }
      }
    
      if (index === targetNumber) {
        for (const item of mapList) {
          if (!item.mark) {
            if (item.isBoom) {
              touchBoom(item);
            } else {
              item.open = true;
              gridSize.nowBoomSize -= 1;
              safeStore.setId(`${item.x},${item.y}`, item);
            }
          }
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    基本上主要思路就这些 其实写下来感觉没有想象中的那么难 当然我这个是比较简单的方式 后续可能还会稍微改改 优化优化 完整项目如下:
    https://github.com/SDSGK/minesweeper

  • 相关阅读:
    Vue--1.6计算属性
    关于CUDA+Torch+TorchVision+Python环境配置问题
    JavaScript之正则表达式
    OpenCV(四十一):图像分割-分水岭法
    Android批量加载图片OOM问题
    在互联网,摸爬滚打了几年,我悟了。面对如今经济形势,普通打工人如何应对?
    C语言迪迦奥特曼变身器✨
    IDEA XML文件里写SQL比较大小条件
    原来Linux这么牛:称霸全球超级电脑 500 强!
    不用开窗标号
  • 原文地址:https://blog.csdn.net/weixin_45615791/article/details/134015584