文章目录

前言

一、扫雷简介

二、步骤

1.创建项目文件结构

2.搭骨架和基础样式(HTML+CSS)

3.添加“动作”(JS主体部分)

总结


前言

本项目仅用于个人学习分享,为各位前端学习小伙伴们提供个人学习过程和总结。本项目涉及的技术栈比较有限(纯HTML+CSS+JS),同时也只由个人单独完成仅具备基本功能,故不排除各种待迭代开发的bug,新手可用于练习JS,大佬也请多多指教。


提示:以下是本篇文章正文内容,下面案例可供参考

一、扫雷简介

扫雷是一款大众类的益智类游戏,于1992年发布,是我个人比较喜欢的游戏之一。最近准备找实习故用这个作为第一个真正练手的项目,共花费一下午时间完成其基本功能,存在的问题还有待解决。

扫雷玩法不做过多赘述,有需要的同学可参考该地址进行了解学习:

扫雷怎么玩(扫雷游戏规则技巧图解) - 华风扬 (uxxsn.com)

下图为三种等级的项目显示效果:

二、步骤

1.创建项目文件结构

文件结构如下(VSCode编译器示例):

2.搭骨架和基础样式(HTML+CSS)

step 1:首先引入创建好的CSS文件和JS文件;

step 2:分析页面结构——由以下三部分组成

整个页面用一个div,主体部分分为三个div,分别是游戏级别(.level)、游戏界面(.gameBox)、剩余雷数(.mineNum);其中.level盒子中每个元素分别是一个button,.gameBox由JS代码动态生成,.div中剩余雷数值用span包装等待JS动态赋值。

代码如下(HTML):

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>hzj的扫雷小游戏</title><link rel="stylesheet" href="css/index.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/index.js"></script>
</body></html>

step 3:书写页面样式

父元素居中放置——margin: 50px auto;

子元素欲在父亲中居中显示——text-align: center;

设置鼠标指针光标样式——cursor: pointer;

默认初级等级为选中状态;

设置雷格阴影效果——border-color: #fff #a1a1a1 #a1a1a1 #fff;

将地雷和小红旗作为背景图片动态显示;

代码如下(CSS):

#mine {margin: 50px auto;
}.level {text-align: center;margin-bottom: 10px;
}.level button {padding: 5px 15px;background: #02a4ad;border: none;color: #fff;/* outline: 3px; */border-radius: 3px;cursor: pointer;
}.level button.active {background: #00abff;
}table {border-spacing: 1px;background: #929196;margin: 0 auto;
}td {padding: 0;width: 20px;height: 20px;background: #ccc;border: 2px solid;border-color: #fff #a1a1a1 #a1a1a1 #fff;text-align: center;line-height: 20px;font-weight: bold;
}td.zero {background-color: #a09f9f;background: #a09f9f;
}td.one {background-color: #a09f9f;background: #a09f9f;color: #0332fe;
}td.two {background-color: #a09f9f;background: #a09f9f;color: #019f02;
}td.three {background-color: #a09f9f;background: #a09f9f;color: #ff2600;
}td.four {background-color: #a09f9f;background: #a09f9f;color: #93208f;
}td.five {background-color: #a09f9f;background: #a09f9f;color: #ff7f29;
}td.six {background-color: #a09f9f;background: #a09f9f;color: #ff3fff;
}td.seven {background-color: #a09f9f;background: #a09f9f;color: #3fffbf;
}td.eight {background-color: #a09f9f;background: #a09f9f;color: #22ee0f;
}.info {margin-top: 10px;text-align: center;
}.mine {background: #d9d9d9 url(../images/miner.png) no-repeat center;background-size: cover;
}.flag {background: #ccc url(../images/flag.png) no-repeat center;background-size: cover;
}

3.添加“动作”(JS主体部分)

本项目对象只有雷,故只需编写一个Mine的构造函数,含有属性tr、td、mineNum分别表示行列以及预定义的雷的数量,同时用一个二维数组squares存储所有方块的信息、tds存储所有单元格的DOM;同时我们还需要有一个常量来记录剩余雷的数量,一开始等于游戏等级中预定义的雷的数量;除此之外,我们还需要一个初值为false的allRight属性,用于判断用户右击数达到雷数时是否扫雷成功;以上一切属性设定完成后,因为我们需要在.gameBox中动态生成扫雷表格,故通过document.querySelector('.gameBox')获取到待添加的父级网页元素。

完成对象构造函数的编写以后,我们接下来将一步步在对象Mine的原型上进行对象方法函数的编写(即Mine.prototype.函数名 = function(){ ...函数方法主体部分... })。

实例方法一:创建表格——createDom()

在该方法中,我们要做的是在网页中动态创建一个扫雷表格——document.createElement('table') & document.createElement('tr') & document.createElement('td');用this.tds存储创建的dom元素并把格子对应的行与列存储到格子自己身上(domTd.pos记录),给domTd添加鼠标点击事件,该事件中调用play函数开始游戏(点击事件可先不写)。

实例方法二:生成随机的雷——randomNum()

先生成一个固定长度(tr*td)的空数组,通过一个循环将0~tr*td放入数组中,再使用一个sort函数将其顺序打乱,最后取其前mineNum个值,默认为雷在表格中的索引位置。

实例方法三:找周围非雷格子——getAround(square)

传入的参数square为中心格子,首先我们可以用两个变量x,y记录下中心点的x,y坐标,定义一个二维数组result用于存储找到的格子。解决该问题我们要知道以下三点:

①中心点与周围点的x(0°方向),y轴(-90°方向)关系

(x-1,y-1) (x,y-1) (x+1,y-1)
(x-1,y) (x,y) (x+1,y)
(x-1,y+1) (x,y+1) (x+1,y+1)

②行列与x,y轴之间的关系——第i行j列对应坐标轴上(j,i)

③遍历循环九宫格时的几种特殊情况——四周(上边、下边、左边、右边)格子超出范围、当前中心格子是自己,周围格子有雷,对应代码处理部分如下;

//通过坐标循环九宫格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]); //以行列形式返回}}

实例方法四:更新所有格子值——updateNum()

双重循环遍历squares的行和列,跳过周围没有雷的方块,只计算更新周围有雷的方块;即碰到一个雷就调用getAround()函数获取雷周围的所有非雷格,并逐个进行value值+1的操作。

注:getAround()函数返回的二维数组设为num,其中num[i]对应一个方格的行列,即num[i]——[a,b];故num[i][0]和num[i][1]分别对应取到方格的行和列。

实例方法五:初始化游戏——init()

第一步调用randomNum()函数生成随机的地雷,遍历表格行和列设置每个格子对应的类型;

//地雷类型
{type:'mine',x:0,y:0
}
//数值类型,value为周围雷的数量
{type:'number',x:0,y:0,value:2
}

第二步更新所有格子值(updateNum()函数);

第三步创建表格;

第四步获取DOM元素,给结构中第三个div动态添加剩余雷数的值,初始化为预定义的雷数。

实例方法六:开始游戏——play()

完善createDom()函数中的单元格点击事件体中的开始游戏函数。

开始游戏时当我们按下键盘按键就会产生keydown, keypress keyup事件,从这些事件中可以获取按键的键值,而键值保存在了 ev.which ev.keyCode ev.charCode中。

keyCode表示用户按下键的实际的编码(用户按下的按键的物理编码),而charCode是指用户按下字符的编码。我们需要知道的是用户点击的是鼠标的左键还是右键,故用ev.which即可处理;ev.which==1表示点击的是左键,ev.which==3则表示点击右键。

首先处理左键点击事件,第一点我们需要知道小红旗是不能进行左键点击的,我们可通过squares和obj.pos获取到当前被点击的单元格;这时我们对该单元格进行类型判断,如果点击到的是数字类型且数值为0,我们就需要进行一个扩展的操作,否则直接显示数值(此处为了更加美观需在CSS中定义不同数值的显示样式),如果点击到的是雷类型,则游戏结束。

其次我们处理右击事件。第一点,如果是一个数字类型的单元格我们是不能点击的;其次每次右击,我们都需要对该单元格进行一个class的切换,即右击即修改其className为flag(小红旗),但如果本身就是一个flag,再次右击则小红旗消失;第二点,每标注一个小红旗我们都需判断其是否为雷,并修改allRight值;第三点,右击产生一个小红旗,则剩余雷数动态减一,小红旗消失剩余雷数加一;最后判断小红旗数是否用尽,若用尽则通过allRight判断游戏是成功还是失败。

重点难点:左键点击事件中怎么处理0的扩展?——递归思想

首先我们考虑一下整个扩展的过程:

  • 1.显示自己(空白)
  • 2.以自己为中心找四周
  • 2.1 显示四周(如果四周值不为0,停止,不再显示)
  • 2.2 如果值为0
  • 2.2.1 显示自己(空白)
  • 2.2.2 找四周(如果四周值不为0,停止,不再显示)
  • 2.2.2.1  ...
  • 2.2.2.2  ...

故为一个递归调用自己的过程——封装为一个getAllZero()函数。

实例方法七:0递归扩展函数——getAllZero(square)

首先获取当前中心点square的四周格子,取出行、列,判断其是否仍为值为0的数字方块,若是,已该方块为中心继续找四周(此时注意,我们需要添加一个check属性去标记某个单元格是否已被递归调用过,避免死循环);若不是则直接显示数字,函数结束。

实例方法八:游戏结束——gameOver()

  1. 显示所有的雷
  2. 取消所有格子的点击事件
  3. 给点中的雷标上红色

代码如下(JS):

//构造函数
function Mine(tr, td, mineNum) {this.tr = tr; //行数this.td = td; //列数this.mineNum = mineNum; //雷的数量//存储所有方块的信息,二维数组,行与列的顺序排放,存取使用行列的形式this.squares = [];//存储所有单元格的DOMthis.tds = [];//剩余雷的数量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;}square.sort(function() { return 0.5 - Math.random() });// console.log(square);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]=;// n++;//取一个方块 在数组里的数据,要使用行与列的形式取,找方块周围的方块用坐标的形式取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 };}}// 0,0  0,1  0,2   行和列// 0,0  1,0  2,0   xy轴// {//     type:'mine',//     x:0,//     y:0// }// {//     type:'number',//     x:0,//     y:0,//     value:2// }}// console.log(this.squares);this.updateNum();this.createDom();this.parent.oncontextmenu = function() {return false;}//剩余雷数this.mineNumDom = document.querySelector('.mineNum');this.mineNumDom.innerHTML = this.surplusMine;}//创建表格
Mine.prototype.createDom = 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');// domTd.innerHTML = 0;domTd.pos = [i, j]; //把格子对应的行与列存到格子身上,为了下面通过这个值取到相应的数字domTd.onmousedown = function(Event) {This.play(Event, this); //大的this指的是实例对象,this指的是点击的Td}//把所有创建的td添加到数组当中this.tds[i][j] = domTd;// if (this.squares[i][j].type == 'mine') {//     domTd.className = 'mine';// }// if (this.squares[i][j].type == 'number') {//     domTd.innerHTML = this.squares[i][j].value;// }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 = []; //找到的格子(<=8个)的坐标  二维数组/*** x-1,y-1    x,y-1     x+1,y-1    * x-1,y      x,y       x+1,y* x-1,y+1    x,y+1     x+1,y+1*///通过坐标循环九宫格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;}//获取到每一个雷周围的数字var num = this.getAround(this.squares[i][j]);// console.log(num);for (var k = 0; k < num.length; k++) {// num[i]==[0,1]// num[i][0]==0// num[i][1]==1this.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') {//点击的是左键,并限制不能左键点击小红旗// console.log(obj);var curSquare = this.squares[obj.pos[0]][obj.pos[1]];var cl = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'];if (curSquare.type == 'number') {//点到数字// console.log('你点到数字了!');//两种情况:点到0和非0obj.innerHTML = curSquare.value;obj.className = cl[curSquare.value];if (curSquare.value == 0) {// 如果点到了0/*** 扩展空白——递归* 1.显示自己(空白)* 1.以自己为中心找四周*      1.1 显示四周(如果四周值不为0,停止,不再显示)*      1.2 如果值为0*              1.2.1 显示自己(空白)*              1.2.2 找四周(如果四周值不为0,停止,不再显示)*                      1.2.2.1  ...*                      1.2.2.2  ...*/obj.innerHTML = ''; //不显示0function getAllZero(square) {//找到了周围n个格子var around = This.getAround(square);for (var i = 0; i < around.length; i++) {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) {//给对应的td添加check属性,用于决定该格子有没有被找过,找过改为true,下一次就不会找了This.tds[x][y].check = true;getAllZero(This.squares[x][y]);}} else {//不为0则显示数字This.tds[x][y].innerHTML = This.squares[x][y].value;}}}getAllZero(curSquare);}} else {//用户点到雷// console.log('你点到雷了!');this.gameOver(obj);}}//点击的是右键if (ev.which == 3) {//如果右击的是一个数字就不能点击if (obj.className && obj.className != 'flag') {return;}obj.className = obj.className == 'flag' ? '' : 'flag'; //切换classif (this.squares[obj.pos[0]][obj.pos[1]].type == 'mine') {//都是雷this.allRight = true;} else {this.allRight = false;}if (obj.className == 'flag') {this.mineNumDom.innerHTML = --this.surplusMine;} else {this.mineNumDom.innerHTML = ++this.surplusMine;}if (this.surplusMine == 0) {//用户标完了小红旗的数量,此时该判断游戏是成功还是结束if (this.allRight) {//该条件成立说明用户全部标对了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 = '#f00';}}//上边button的功能
var btns = document.querySelectorAll('.level button');
var mine = null; //存储生成的实例
var ln = 0; //用于处理当前选中的level状态
var arr = [[9, 9, 9],[16, 16, 40],[28, 28, 99]
]; //不同level的行数列数及雷数for (let i = 0; i < btns.length - 1; i++) {btns[i].onclick = function() {btns[ln].className = '';this.className = 'active';//ES6扩展运算符mine = new Mine(...arr[i]);mine.init();// mine.surplusMine = arr[i][3];ln = i;}
}
btns[0].onclick(); //主动调事件,初始化一下
btns[3].onclick = function() {mine.init();
}// var mine = new Mine(28, 28, 99);
// mine.init();// console.log(mine.getAround(mine.squares[0][0]));

总结

以上就是我个人完成扫雷项目的整体框架流程和代码,由于时间原因仍留有一定bug(如每次点击level重新开始一次游戏,剩余雷数未进行初始化等问题),该项目也还有很多地方可以右更好的办法进行优化,今天的博客就分享到这里啦~如果往后我有时间对该项目进行了迭代开发优化,会尽量分享给小伙伴们滴!

纯JS实现简易扫雷小游戏网页项目相关推荐

  1. 扫雷html5简单初级,纯原生JS用面向对象class方法实现简易扫雷小游戏

    Demo介绍 纯原生js 实现 且用ES6语法class写的扫雷小游戏:布局为10*10的网格,随机生成10-20个雷. 左键点击扫雷.右键标记该地方有雷.该demo下载下来复制到html文件中直接可 ...

  2. 纯原生JS用面向对象class方法实现简易扫雷小游戏

    Demo介绍 纯原生js 实现 且用ES6语法class写的扫雷小游戏:布局为10*10的网格,随机生成10-20个雷. 左键点击扫雷.右键标记该地方有雷.该demo下载下来复制到html文件中直接可 ...

  3. 用html+css+js实现中国象棋小游戏开发项目

    用html+css+js中国象棋小游戏开发项目 最近刚学习完JS的相关课程,跟着老师做了两个小游戏项目,就已经抑制不住内心的小激动,想着要迫不及待的着手准备做一个网页小游戏--中国象棋 由于从小就比较 ...

  4. C语言实现简易扫雷小游戏

    扫雷(简易版): 游戏规则:电脑随机生成雷,玩家随机扫一个坐标,如果该坐标是生成雷的位置,则踩到雷.如果没有则显示该坐标附近八个坐标雷的总数,一直循环至所有不是雷的坐标全部扫完 下面图片红色代表雷,黑 ...

  5. C语言大一课设 扫雷小游戏

    目录 [题    目]扫雷小游戏 [项目简介] [总体功能结构流程图] 登录和注册流程图: 打印棋盘流程图: 计算周围雷的个数流程图: test.c game.h game.c 运行结果 (1)注册运 ...

  6. 也分享自己做的JS扫雷小游戏

    看了草根程序猿分享的JS扫雷小游戏 想起去年的时候自己也做了一个 于是也拿出来分享之 先上截图~ 引用了jQuery,节省了很多鼠标点击上的判断 界面显然都是照搬Windows的扫雷啦 详细的内容注释 ...

  7. C语言实现扫雷小游戏 纯小白 非黑窗口

    C语言实现一个普通的扫雷小游戏 纯小白所编(含代码非黑窗口!) 扫雷 主要功能 1.创建一个图形界面 2.了解扫雷游戏的原理 3.随机生成雷的位置 4.为整个数组加密,并在雷周围的位置加一 5.导入图 ...

  8. JS扫雷小游戏(DOM (html+css+js))

    一:效果图: 二:资源 js扫雷小游戏源代码下载地址 js贪吃蛇小游戏链接 python小黄脸大战小游戏链接 vue高仿网易云音乐app 三:源代码: html部分: <!DOCTYPE htm ...

  9. C实现扫雷小游戏(简易版)

    你知道,有些鸟儿是注定不会被关在牢笼里的,它们的每一片羽毛都闪耀着自由的光辉.--<肖申克的救赎> 目录 1.设计框架 2.设计流程 2.1菜单 2.2初始化雷阵 2.3生成雷 2.4玩家 ...

  10. c语言小游戏 精简_C语言开发简易版扫雷小游戏

    C 语言开发简易版扫雷小游戏 本文给大家分享的是一个使用 C 语言开发的命令行下的简易版扫雷小游戏, 本身没有什么太 多的技术含量, 只不过是笔者的处女作, 所以还是推荐给大家, 希望对大家学习 C ...

最新文章

  1. C#程序以管理员权限运行
  2. trove mysql 镜像_Linux运维----03.制作trove-mysql5.7镜像
  3. 几个超级实用但很少人知道的 VS 技巧[更新]
  4. 大白菜pe解锁bitlocker_微PE工具箱 v2.1 官方版,最好用的 Win10PE 系统
  5. 信息学奥赛一本通(1085:球弹跳高度的计算)
  6. python nltk book_nltk book的下载
  7. 中国“两高”发布司法解释 依法严惩涉地下钱庄犯罪
  8. 北大助理教授献给「后浪」的建议:奔涌吧,深度学习!
  9. 微软职位内部推荐-Software Engineer II-Senior Software Engineer for Satori
  10. Android中字体文件位置
  11. PMP第十一章:项目风险管理
  12. 关于tagLyst工具授权验证的分析报告
  13. 关于广告投放系统:广告竞拍(2018)
  14. Java二维码编码识别
  15. OpenCL设备存储形式
  16. 数据库原理与应用(Oracle) 笔记1 —— SQL语句和SQL函数
  17. java之父那一年出生_黄小戈哪一年出生 起底其详细资料背景曝真实年龄多少
  18. EeePC 1005ha(1008ha)安装Ubuntu的完美方案
  19. 主要的竞争情报分析研究方法
  20. java里precision,int precision()

热门文章

  1. java中的缓存详解,一篇就够了
  2. linux虚拟机系统下安装jdk
  3. mac上SCRT中文变问号的处理方式,亲测有效
  4. 实变函数与计算机有关系吗,实变函数论文.doc
  5. java笔试试题含答案_Java笔试题带答案
  6. Datalogic 得利捷最新推出Matrix 320 5MP:不仅限于内部物流追溯
  7. 通讯简单测试—Modscan32使用简介-Susie 周
  8. Python处理QXDM抓取log
  9. 使用devcon禁用启用网卡
  10. 数学建模之微分方程模型详解