原生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 生成网格信息

实现思路:

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' : {
	...
}

实现代码如下:


// 初始化表格数据
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 第一次开图

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

// 获取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;
      }
    }
  }
}

其中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.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.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.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);
        }
      }
    }
  }
}

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值