JavaScript实现扫雷游戏:动态生成网格与核心逻辑解析
本文详细介绍如何使用JavaScript开发一个简易版扫雷游戏。通过动态生成HTML表格构建游戏界面,利用二维数组存储格子状态(雷或数字),并实现周围地雷计数逻辑。文章涵盖构造方法定义、DOM创建及事件绑定,适合前端初学者参考学习。
JavaScript实现扫雷游戏:动态生成网格与核心逻辑解析
本文分享了一个基于JavaScript实现的扫雷游戏完整代码示例,供读者参考学习。
界面设计
界面设计
为了适配简单、一般和困难三种不同难度,各难度下的扫雷区域格子数量存在差异,因此游戏界面中的扫雷区域由JavaScript动态生成。首先搭建整体的HTML框架结构:
<div class="container">
<div class="level">
<button class="select">简单</button>
<button>一般</button>
<button>困难</button>
<button>重新开始</button>
</div>
<div class="mine">
//扫雷区域
</div>
<div class="last">总共雷数 : <span class="mineNum"></span></div>
</div>构造函数定义
接下来定义一个名为MineSweeper()的构造函数:
function MineSweeper(tr, td, mineNum) {
this.tr = tr; //行
this.td = td; //列
this.mineNum = mineNum; //雷数
this.area = []; //存取每个格子信息
this.doms = []; //存储格子DOM,用来动态创建DOM
this.lastMineNum = mineNum; //剩余雷数
this.parent = document.querySelector('.mine');
this.num = document.querySelector('.last .mineNum');
}数据结构与DOM生成
扫雷区域的数据结构采用二维数组进行表示和存储:
/*
* 扫雷游戏区域area(二维数组)
* [
* [type: mine/number, x1, y1],
* [type: mine/number, x2, y2],
* ... ,
* [type: mine/number, xn, yn]
* ]
*/数组元素中的type字段包含两个值:mine代表当前格子为地雷,number代表当前格子为数字。在构造函数的原型上添加方法以创建DOM表格,从而实现扫雷区域界面的动态生成:
MineSweeper.prototype.create = function() {
var _this = this;
var table = document.createElement('table');
for(var i = 0; i < this.tr; i++) {
var trDom = document.createElement('tr');
//创建行
this.doms[i] = [];
for(var j = 0; j < this.td; j++) {
//创建列
var tdDom = document.createElement('td');
this.doms[i][j] = tdDom;
trDom.appendChild(tdDom); //往行中添加列
}
table.appendChild(trDom);//往table中添加行
}
this.parent.appendChild(table); //将table添加到界面
};JS代码部分
function MineSweeper(tr, td, mineNum) {
this.tr = tr; //行
this.td = td; //列
this.mineNum = mineNum;
this.area = [];
this.doms = [];
this.lastMineNum = mineNum;
this.parent = document.querySelector('.mine');
this.num = document.querySelector('.last .mineNum');
}
//初始化
MineSweeper.prototype.init = function() {
var rn = this.randomNum(); //获得type: mine 的索引
var n = 0; //记录格子索引
for(var i = 0; i < this.tr; i++) {
this.area[i] = [];
for(var j = 0; j < this.td; j++) {
n ++;
if(rn.indexOf(n) !== -1) {
this.area[i][j] = {
type: 'mine',
x: j,
y: i
};
} else {
this.area[i][j] = {
type: 'number',
x: j,
y: i,
value: 0
};
}
}
}
// console.log(this.area);
this.num.innerHTML = this.mineNum; //初始化雷数
this.parent.oncontextmenu = function() {
return false; //阻止右击菜单事件
}
this.updateNumber();
//创建表格
this.parent.innerHTML = "";
this.create();
}
//创建DOM表格
MineSweeper.prototype.create = function() {
var _this = this;
var table = document.createElement('table');
for(var i = 0; i < this.tr; i++) {
var trDom = document.createElement('tr');
this.doms[i] = [];
for(var j = 0; j < this.td; j++) {
var tdDom = document.createElement('td');
this.doms[i][j] = tdDom;
trDom.appendChild(tdDom);
tdDom.pos = [i, j];
tdDom.onmousedown = function(event) {
if(event.button === 0) { //鼠标左键
var curArea = _this.area[this.pos[0]][this.pos[1]];
console.log(curArea)
if(curArea.type === 'mine') {
// console.log('踩到雷了!')
this.className = 'mine';
_this.gameOver(this);
} else {
// console.log('is number')
if(!curArea.value) { //踩到0,出现一大片
// console.log(0);
this.className = 'select'; //先显示自己
this.innerHTML = '';
function getAllZero(area) {
var around = _this.mineAround(area); //找其周围的格子
for(var i = 0; i < around.length; i++) {
var x = around[i][0]; //行
var y = around[i][1]; //列
_this.doms[x][y].className = 'select';
if(!_this.area[x][y].value) {
if(!_this.doms[x][y].isHas) {
_this.doms[x][y].isHas = true; //标记被找过的元素,避免格子重复重复被调用,导致内存资源被滥用
arguments.callee(_this.area[x][y])
}
} else {
_this.doms[x][y].innerHTML = _this.area[x][y].value;
}
}
}
getAllZero(curArea);
} else {
this.className = 'select';
this.innerHTML = curArea.value;
}
}
} else if(event.button === 2) { //鼠标右键
this.className = this.className == 'flag'? '':'flag';
//标记小旗子,则剩余雷数-1
if(this.className === 'flag') {
_this.num.innerHTML = --_this.lastMineNum;
} else {
_this.num.innerHTML = ++_this.lastMineNum;
}
}
}
}
table.appendChild(trDom);
}
this.parent.appendChild(table);
};
//生成指定数量的不重复的数字
MineSweeper.prototype.randomNum = function() {
var mineArr = new Array(this.tr*this.td); //该数组用来存储所有格子下标
for(var i = 0; i < mineArr.length; i++) {
mineArr[i] = i;
}
mineArr.sort(function() {return 0.5 - Math.random()}); //将数组乱序排序
return mineArr.slice(0, this.mineNum); //随机取得放置雷的下标
};
//找目标格子周围的格子, 雷周围的格子都需要number++
MineSweeper.prototype.mineAround = function(target) {
var x = target.x;
var y = target.y;
var result = []; //二位数组,存储周围格子的坐标
for(var i = x-1; i <= x+1; i++) {
for(var j = y-1; j <= y+1; j++) {
if(
i < 0 || j < 0 || i > this.td - 1 || j > this.tr - 1 || //排除四个角
(i == x && j == y) || //排除周围是雷
this.area[j][i].type === 'mine'
){
continue;
}
result.push([j, i]);
}
}
return result;
};
//更新所有数字
MineSweeper.prototype.updateNumber = function() {
for(var i = 0; i < this.tr; i++) {
for(var j = 0; j < this.td; j++) {
if(this.area[i][j].type == 'number') {
continue;
}
var nums = this.mineAround(this.area[i][j]); //获取雷周围的格子
for(var k = 0; k < nums.length; k++) {
//雷周围的格子的number都要+1
this.area[nums[k][0]][nums[k][1]].value += 1;
}
}
}
};
//gameOver
MineSweeper.prototype.gameOver = function(downMine) {
for(var i = 0; i < this.tr; i++) {
for(var j = 0; j < this.td; j++) {
if(this.area[i][j].type === 'mine') {
this.doms[i][j].className = 'mine';
}
this.doms[i][j].onmousedown = null;
}
}
if(downMine) {
downMine.style.backgroundColor = '#f40';
}
}
function startGame() {
var btn = document.querySelectorAll('.container .level>button');
var arr = [[10,10,15],[15,15,40],[20,20,80]];
var select = 0; //当前选中状态的按钮
var mine = null;
for(let i = 0; i < btn.length - 1; i++) {
console.log(i);
console.log(arr);
btn[i].onclick = function(e) {
btn[select].className = '';
this.className = 'select';
select = i;
mine = new MineSweeper(...arr[i]);
console.log(arr[i]);
mine.init();
}
}
btn[0].onclick();
btn[3].onclick = function() {
mine.init();
}
}
startGame();CSS样式部分
.container {
margin: 30px auto;
text-align: center;
}
.container .level button {
background-color: #e5c1cd;
outline-style: none;
border: none;
cursor: pointer;
color: #fff;
}
.container .level button.select {
background-color: #b6b4c2;
}
.container .mine table {
border-spacing: 1px;
margin: 10px auto;
}
.container .mine table td {
width: 18px;
height: 18px;
padding: 0;
background-color: #bfb8da;
border: 2px solid;
border-color: #ebd7d4 #a56781 #a56781 #ebd7d4;
text-align: center;
line-height: 16px;
font-weight: bold;
}
.container .mine table td.select,
.container .mine table td.mine {
background-color: #bfb8da;
border: 1px solid #ebd7d4;
}
.container .mine table td.mine {
background-image: url("../image/mine.png");
background-repeat: no-repeat;
background-size: cover;
}
.container .mine table td.flag {
background-image: url("../image/flag.png");
background-repeat: no-repeat;
background-size: cover;
}
.container .last {
color: #d87f81;
}HTML结构部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MineSweeper</title>
<link rel="stylesheet" href="./css/index.css">
</head>
<body>
<div class="container">
<div class="level">
<button class="select">简单</button>
<button>一般</button>
<button>困难</button>
<button>重新开始</button>
</div>
<div class="mine">
</div>
<div class="last">总共雷数 : <span class="mineNum"></span></div>
</div>
<script src="./js/index.js"></script>
</body>
</html>数字更新逻辑
数字更新逻辑
遍历扫雷区域的二维数组,当检测到某个格子为雷时,获取其周围相邻格子的信息。如果周围格子类型为数字,则将其number值加1;如果周围格子也是雷,则对该格子不作任何操作。
MineSweeper.prototype.updateNumber = function() {
for(var i = 0; i < this.tr; i++) {
for(var j = 0; j < this.td; j++) {
if(this.area[i][j].type == 'number') {
continue; //遇到数字格子跳过,不需要取其周围的格子
}
var nums = this.mineAround(this.area[i][j]); //获取雷周围的格子
for(var k = 0; k < nums.length; k++) {
//雷周围的格子的number都要+1
this.area[nums[k][0]][nums[k][1]].value += 1;
}
}
}
};最终实现效果如下:

以上就是本文的全部内容,希望能为各位的学习提供有益的帮助,也欢迎大家继续支持本站。
怎么用 Promise 封装 JavaScript sleep 函数实现异步延时
2026-11-05 17:49:14
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
