原生js开发扫雷
扫雷
扫雷作为一款复古游戏那么他是怎么被编写出来的呢今天我们就来使用JavaScript来编写一款自己的扫雷游戏。
先看成品
初始化游戏界面:
难度选择:
初级难度
中级难度
高级难度
思路
生成游戏棋盘
- 利用双层for循环创建设定的棋盘大小
- 为每个单元格的dom元素创建一个属性,该属性用于保存单元格的所有信息,如x,y坐标,value,是否为雷等
随机生成炸弹
- 利用随机数,随机生成炸弹x,y坐标,并将符合该坐标信息的单元格的属性更改为雷
- 炸弹是在用户第一次点击的时候生成,防止用户第一次点击到炸弹
- 将生成的每个炸弹信息都保存到一个this变量中,方便后续使用
- 遍历每个炸弹周围的非炸弹方格,每遍历一次value值+1
鼠标左键点击
- 点击的时候需要考虑该单元格是否有被标记小旗子,如果有则无法点击
- 判断是雷还是数字,雷的话则游戏结束,数字则继续判断是否等于0,等于0则使用递归显示空白区域
- 每次打开一个单元格,需要更改该单元格的属性,表示单元格被打开
鼠标右键点击
- 点击时需要考虑该单元格的属性是否被打开,打开的话则无法点击
- 当该单元格没有标记旗帜时标记,如果有标记旗帜则取消标记
- 每标记一个方格,剩余炸弹数量-1,取消标记则+1
游戏结束
- 当左键点击到炸弹的时候游戏结束。失败
- 剩余炸弹数量为0时。判断旗帜标记是否正确,正确游戏胜利,标记有误则失败
代码
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="./css/1.css" />
</head>
<body>
<div id="mine">
<div class="level">
<button class="active">初级</button>
<button>中级</button>
<button>高级</button>
<button>重新开始</button>
</div>
<div class="gameBox"></div>
<div class="info">剩余的雷数:<span class="mineNum"></span></div>
</div>
<script src="./js/saolei.js"></script>
</body>
</html>
css
#mine {
margin: 50px auto;
}
.level {
text-align: center;
margin-bottom: 10px;
}
.level button {
border: none;
border-radius: 10px;
padding: 5px 15px;
cursor: pointer;
background: cadetblue;
outline: none;
}
.level button.active {
background: chartreuse;
}
table {
/* 格子之间的缝隙 */
margin: 0 auto;
border-spacing: 1px;
background: cornflowerblue;
}
td {
padding: 0;
width: 20px;
height: 20px;
background: #ccc;
border: 2px solid;
border-color: #fff #a1a1a1 #a1a1a1 #fff;
line-height: 20px;
text-align: center;
font-weight: bold;
}
td.zero {
border-color: #d9d9d9;
background: #d9d9d9;
}
td.one {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(6, 136, 12);
}
td.two {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(19, 128, 201);
}
td.three {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(11, 61, 128);
}
td.four {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(132, 8, 143);
}
td.five {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(245, 83, 19);
}
td.six {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(224, 105, 8);
}
td.seven {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(233, 2, 2);
}
td.eight {
border-color: #d9d9d9;
background: #d9d9d9;
color: rgb(255, 0, 0);
}
.info {
margin-top: 10px;
text-align: center;
}
.mine {
background: #d9d9d9 url(../img/lei.png) no-repeat center;
background-size: cover;
}
.flag {
background: #cccccc url(../img/qizi.png) no-repeat center;
background-size: cover;
}
Js
//面向对象的 扫雷
var arr3 = [];
function Mine (tr, td, mineNum) {
this.tr = tr; //行数
this.td = td; //列数
this.mineNum = mineNum //雷数
this.squares = [] //存储所有的方块信息;是一个二维数组 按行列的顺序排放的,存取都是行列的形式;
this.tds = []; //存储所有的单元格的dom信息
this.surplusMine = mineNum; //剩余雷的数量
this.allRight = false; //右击的小红旗 判断是否全是雷
this.parent = document.querySelector('.gameBox');
}
//创建一个表格类
//生成n个 不重复的数字
Mine.prototype.randomNum = function () {
var square = new Array(this.tr * this.td); //生成一个 空数组但是有长度 ,长度为 格子的总数 。
for (var i = 0; i < square.length; i++) {
square[i] = i;
}
//取到99个不重复的数据 用了sort随机排序的原理
square.sort(function () {
return 0.5 - Math.random()
});
return square.slice(0, this.mineNum);
}
//初始化
Mine.prototype.init = function () {
// console.log(this.randomNum());
var rn = this.randomNum(); //雷在格子里面的位置
var n = 0; //用来找到格子对应的索引
for (var i = 0; i < this.tr; i++) {
this.squares[i] = []; //所有格子所对应的信息
for (var j = 0; j < this.td; j++) {
// this.squares[i][j] = ;
//取一个方块 要用行列的数据形式去取。我方周围的方块要用坐标的形式去取的x,y 是刚好相反的
if (rn.indexOf(++n) != -1) {
//如果这个条件成立 就说明循环到这个索引在雷的数组里面找到了。说明这索引对应的是个雷
this.squares[i][j] = {
type: 'mine',
x: j,
y: i
}
} else {
this.squares[i][j] = {
type: 'number',
x: j,
y: i,
value: 0
}
}
}
}
this.updateNum();
this.creatDom();
this.parent.oncontextmenu = function () {
return false;
}
// console.log(this.squares);
//剩余雷的数量
this.mineNumDom = document.querySelector('.mineNum');
this.mineNumDom.innerHTML = this.surplusMine;
}
Mine.prototype.creatDom = function () {
var This = this;
var table = document.createElement('table');
for (var i = 0; i < this.tr; i++) { //行
var domTr = document.createElement('tr');
this.tds[i] = [];
for (var j = 0; j < this.td; j++) {
var domTd = document.createElement('td')
this.tds[i][j] = domTd; //把所有的创建的td添加到数组当中
// domTd.innerHTML = 0;
domTd.pos = [i, j]; // 把这个格子对应的行与列存到格子身上,为了下面通过这个值去数组里面取到对应的数据;
domTd.onmousedown = function () {
This.play(event, this); //大this是 指实例对象 小的this是指点击事件
}
domTr.appendChild(domTd);
}
table.appendChild(domTr);
}
this.parent.innerHTML = ''; //避免创建多个
this.parent.appendChild(table);
}
//用来找格子的周围 八个格子
Mine.prototype.getAround = function (square) {
var x = square.x;
var y = square.y;
var result = []; //把找到的格子的坐标返回出去(二维数组)
//双层循环 循环出来 九个格子
//通过坐标去循环的九宫格 存取的都是用的行列的形式 所以下面一定要用ji
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.squares[j][i].type == 'mine' //周围的格子是雷的条件 自己的坐标不要
) {
continue; //跳出这次循环
}
result.push([j, i]); //要以行与猎的形式返回出去,因为到时候要用他取数组的数据
}
}
return result;
}
//更新所有的数字。把他变成 对应正确的数字
Mine.prototype.updateNum = function () {
for (var i = 0; i < this.tr; i++) {
for (var j = 0; j < this.td; j++) {
// 更新的不是所有的坐标 只更新雷周围的数字
if (this.squares[i][j].type == 'number') {
continue; //如果 type 意思找的是非雷的数字 所以直接跳过
} else {
var num = this.getAround(this.squares[i][j]); //获取到每一个累周围的数字
// console.log(num);
for (var k = 0; k < num.length; k++) {
// 这里 num【k】【0】就是代表 雷周围的格子的位置 就是让 累周围的格子的位置的value+1
this.squares[num[k][0]][num[k][1]].value += 1;
}
}
}
}
// console.log(this.squares);
}
Mine.prototype.play = function (ev, obj) {
var This = this;
if (ev.which == 1 && obj.className != 'flag') //限制用户 标完小红旗之后 不能再次点击左键了
{
// 点击的是左键
var curSquare = this.squares[obj.pos[0]][obj.pos[1]];
var cl = ['zero', 'one', 'two', 'three', 'four', 'six', 'seven', 'eight']
//先处理 点到了数字
if (curSquare.type == 'number') {
obj.innerHTML = curSquare.value;
obj.className = cl[curSquare.value];
if (curSquare.value == 0) {
//用户点到数字0;
//如果是零他就不显示
/*如何变成点击到这个空格之后扩展到一大片 要使用到递归
以整个零为中心点找周围的八个格子有没有0
如果 有就继续找周围的八个格子 直到周围的格子都没有零了终止
1.显示自己
2.找周围的八个格子
1.显示四周(如果四周不为零 就显示到这里为止不需要在找了)
2.如果值为零 然后就重复 上述步骤
*/
obj.innerHTML = "";
function getAllZero (square) {
var around = This.getAround(square); //找到了周围的n个格子 返回的是一个二维数组
for (var i = 0; i < around.length; i++) {
// around[i] = [0][0]
var x = around[i][0]; //行
var y = around[i][1]; //列
This.tds[x][y].className = cl[This.squares[x][y].value];
if (This.squares[x][y].value == 0) {
//如果你找到的某个格子的值是0 就以他为中心就继续调
if (!This.tds[x][y].check) {
// 给这个格子添加一条属性 如果没有这条属性就给他加上true
//下一次就不会再找了
This.tds[x][y].check = true;
getAllZero(This.squares[x][y]);
}
} else {
//如果以某个格子的为中心找到的四周的格子不为零就把他人家四周的数字显示出来
This.tds[x][y].innerHTML = This.squares[x][y].value;
}
}
}
getAllZero(curSquare);
}
} else {
//用户点到了雷
this.gameOver(obj);
}
}
if (ev.which == 3) {
//表示用户点击的是右键。
//如果是右击 的是数字 就不能点击
if (obj.className && obj.className != 'flag') {
return;
}
obj.className = obj.className == 'flag' ? '' : 'flag'; //切换小红旗显示隐藏
if (this.squares[obj.pos[0]][obj.pos[1]].type == 'mine') {
this.allRight = true; // 用户表的小红旗背后都是雷
// console.log(obj.getAttribute('data'));
//设置一个限制条件 让点击过的 元素不会重复被 push进数组里面
if (obj.getAttribute('data') != 1) {
arr3.push(this.allRight);
obj.setAttribute('data', 1);
}
} else {
this.allRight = false;
console.log(obj.getAttribute('data'));
if (obj.getAttribute('data') != 1) {
arr3.push(this.allRight);
obj.setAttribute('data', 1);
}
}
// console.log(arr3);
if (obj.className == 'flag') {
//通关判断 flage存在的数量 来判断 游戏存在的雷的数量
this.mineNumDom.innerHTML = --this.surplusMine;
} else {
this.mineNumDom.innerHTML = ++this.surplusMine;
}
// 剩余雷的数量为零 表示用户标完小红旗了 ,这时候要判断游戏是成功还是失败
if (this.surplusMine == 0) {
var a3 = arr3.every(function (v) {
return v == true;
})
console.log(a3);
if (a3) {
alert('恭喜你游戏成功');
} else {
alert('游戏失败 你个废物');
this.gameOver(); //调用 游戏结束的方法
}
}
}
};
//定义一下游戏失败的函数
Mine.prototype.gameOver = function (clickTd) {
/*
1.显示所有的雷
2.页面中的内容点击不动了 取消所有格子的点击事件
3.给点击到的雷标记一个红色
*/
for (var i = 0; i < this.tr; i++) {
for (var j = 0; j < this.td; j++) {
if (this.squares[i][j].type == 'mine') {
this.tds[i][j].className = 'mine';
}
this.tds[i][j].onmousedown = null;
}
}
if (clickTd) {
clickTd.style.backgroundColor = 'red';
alert('游戏失败');
}
}
//button 的功能
var btns = document.querySelectorAll('.level button');
var min = null; //用来存储生成的实例
var Ln = 0; // 用来处理当前选中的状态
var arr = [
[9, 9, 10],
[16, 16, 40],
[28, 28, 99]
] //不同级别的行列以及 雷数
for (var i = 0; i < btns.length - 1; i++) {
(function (i) {
btns[i].onclick = function () {
btns[Ln].className = '';
this.className = 'active';
mine = new Mine(...arr[i]);
mine.init();
Ln = i;
btns[3].onclick = function () {
mine = new Mine(...arr[Ln]);
mine.init();
arr3 = [];
}
}
}(i))
}
btns[0].onclick(); //事件是可以直接调用的。 初始化一开始的东西
转载自:https://juejin.cn/post/7089397624649826312