相信大家都玩过扫雷这个经典的小游戏,它规则简单但耐玩。你有没有想过自己动手开发一个呢?今天我们就教你做一个网页版的扫雷,先上一张效果截图:

知识点

  • javascript

  • css3

  • 扫雷原理

实验原理

在开始开发之前,我们先来设计一下游戏算法。

扫雷游戏的规则很简单:

游戏面板上有一些格子,每个格子中有一个数字(空白表示数字为 0)或是地雷,格子中的数字表示格子周围格子中地雷的数量。玩家要做的就是把数字格子找出来,时间花的越少越好。

除边界上的格子外,每个格子周围有 8 个格子:上、下、左、右、4 个斜角。所以数字范围是 0~8。

所以我们的算法如下:

根据用户选择的难易程度(有初、中、高三个级别,级别越高地雷和格子数量越多),随机产生一定个数的地雷并随机放在格子中。然后遍历格子,计算每个格子中的数字,标记在格子上。玩家左键点击格子时显示格子内容(如果遇到地雷则挑战失败,游戏结束),右键点击格子时标记格子为地雷,真到正确标记所有地雷并打开所有非地雷格子,挑战成功,游戏结束。

小技巧:由于格子中数字范围是 0~8,所以为了便于计算,我们可以把地雷所在的格子中的数字记为 9。

代码获取

$ git clone https://github.com/shiyanlou/js-minesweeper

先建立下面的目录结构:

minesweeper|__index.html|__index.css|__index.js|__jms.js

然后我们需要在项目目录中放入实验要用到的地雷和旗帜的图片, 通过下面的命令下载图片:

$ wget http://labfile.oss.aliyuncs.com/courses/144/mine.png
$ wget http://labfile.oss.aliyuncs.com/courses/144/flag.png

实验步骤

下面将从页面布局开始,一步一步完成所有代码的编写。

页面布局

首先我们需要有一个面板来显示游戏信息,包括剩余地雷个数、所用时间、难度级别等。因为格子数量不是固定的,所以我们先不画格子,放在 JS 代码中绘制。

创建 index.html 文件

添加如下代码并保存:

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>JavaScript版扫雷</title><link rel="stylesheet" type="text/css" href="index.css"/>
</head>
<body><div id="JMS_main" class="main"><table id="landmine"></table><div id="operation"><div class="tip">剩余雷数:<span class="light red" id="landMineCount">0</span> 个</div><div class="tip">持续时间: <span class="light f60" id="costTime">0</span> 秒</div><fieldset><legend>难度选择:</legend><input type="radio" name="level" id="llevel" checked="checked" value="10" /><label for="llevel">初级(10*10)</label><br /><input type="radio" name="level" id="mlevel" value="15" /><label for="mlevel">中级(15*15)</label><br /><input type="radio" name="level" id="hlevel" value="20" /><label for="hlevel">高级(20*20)</label><br /></fieldset><input type="button" id="begin" value="开始游戏" /><br /><div class="tip txtleft">提示:<ul><li>1、点击“开始游戏”游戏开始计时</li><li>2、游戏过程中点击“开始游戏”将开始新游戏</li></ul></div></div></div><script src="jms.js"></script><script src="index.js"></script>
</body>
</html>

页面布局样式

然后需要调整面板上游戏信息的位置,添加一些样式,在 index.css 中添加如下代码并保存:

.main {margin:10px auto;padding:20px;background:#EEE;width:600px;zoom:1;
}
.main table {background:#CCC;float:left;
}
.main table td {border:2px outset #EEE;font-size:20px;width:32px;height:32px;text-align:center;cursor:pointer;
}
.main table td:hover {background-color:#AAA;
}
.main #operation {width:180px;float:right;text-align:center;
}
.landMine {background-image:url(mine.png);background-position:center;background-repeat:no-repeat;
}
.main table td.normal {border:2px solid #EEE;background-color:#AAA;
}
.main table td.normal:hover {background-color:#AAA;
}
.flag {background-image:url(flag.png);background-position:center;background-repeat:no-repeat;
}
.main:after {clear: both;display: block;content: "";line-height: 0;height: 0;visibility:hidden;
}
.main .tip {font-size:14px;margin:5px;
}
.main .tip ul {
}
.main .tip ul li {margin:5px 0;line-height:20px;
}
.main .light{font-size:30px;
}
.main .red {color:red;
}
.main .f60 {color:#F60;
}
.main input[type=button] {padding:2px 10px;margin:5px;font-size:20px;cursor:pointer;
}
.main .txtleft {text-align:left;
}
.main input[type='radio'],
.main fieldset label {cursor:pointer;
}
.main fieldset {margin:10px 0;line-height:25px;
}

完成这步后,在 Preview 或 Mini Browser 中打开 index.html,效果如下:

左边空白处用于显示格子。

绘制格子

完成上面的步骤后,下面就要画格子了,为了让代码更清晰,我们把游戏实现部分和调用部分分开,游戏实现部分放在跟 index.html 同目录下的 jms.js 中,游戏调用部分放在同目录下的 index.js 中。

画格子需要传入一些参数,如放格子的表格的 id,格子的数量(用行数和列数表示)。另外,游戏的其他数据也要进行初始化。

//在jms.js中
(function () {// 初始化扫雷对象,初始化数据var JMS = function (id,rowCount,colCount, minLandMineCount, maxLandMineCount) {if (!(this instanceof JMS))return new JMS(id, rowCount, colCount, minLandMineCount, maxLandMineCount);this.doc = document;this.table = this.doc.getElementById(id);//画格子的表格this.cells = this.table.getElementsByTagName("td");//小格子this.rowCount = rowCount || 10;//格子行数this.colCount = colCount || 10;//格子列数this.landMineCount = 0;//地雷个数this.markLandMineCount = 0;//标记的地雷个数this.minLandMineCount = minLandMineCount || 10;//地雷最少个数this.maxLandMineCount = maxLandMineCount || 20;//地雷最多个数this.arrs = [];//格子对应的数组this.beginTime = null;//游戏开始时间this.endTime = null;//游戏结束时间this.currentSetpCount = 0;//当前走的步数this.endCallBack = null;//游戏结束时的回调函数this.landMineCallBack = null;//标记为地雷时更新剩余地雷个数的回调函数this.doc.oncontextmenu = function () {//禁用右键菜单return false;};this.drawMap();};// 在 JMS 的原型中创建格子JMS.prototype = {//画格子drawMap: function () {var tds = [];// 为了兼容浏览器if (window.ActiveXObject && parseInt(navigator.userAgent.match(/msie ([\d.]+)/i)[1]) < 8) {// 创建引入新的 css 样式文件var css = '#JMS_main table td{background-color:#888;}',// 获取 head 标签head = this.doc.getElementsByTagName("head")[0],// 创建 style 标签style = this.doc.createElement("style");style.type = "text/css";if (style.styleSheet) {// 将 css 样式赋给 style 标签style.styleSheet.cssText = css;} else {// 在 style 标签中创建节点style.appendChild(this.doc.createTextNode(css));}// 再将 style 标签创建为 head 标签的子标签head.appendChild(style);}// 循环创建表格for (var i = 0; i < this.rowCount; i++) {tds.push("<tr>");for (var j = 0; j < this.colCount; j++) {tds.push("<td id='m_" + i + "_" + j + "'></td>");}tds.push("</tr>");}this.setTableInnerHTML(this.table, tds.join(""));},//添加HTML到TablesetTableInnerHTML: function (table, html) {if (navigator && navigator.userAgent.match(/msie/i)) {// 在 table 的 owner document 内创建 divvar temp = table.ownerDocument.createElement('div');// 创建表格的 tbody 内容temp.innerHTML = '<table><tbody>' + html + '</tbody></table>';if (table.tBodies.length == 0) {var tbody = document.createElement("tbody");table.appendChild(tbody);}table.replaceChild(temp.firstChild.firstChild, table.tBodies[0]);} else {table.innerHTML = html;}}};window.JMS = JMS;
})();

上面的代码中,部分代码是为了兼容 IE 浏览器,可忽略。

在 index.js 的调用代码中,我们需要绑定难度选择按钮的事件,然后调用上面定义的 JMS,开始绘制格子。

index.js 中添加如下代码并保存:

//在index.js中
var jms = null,timeHandle = null;
window.onload = function () {var radios = document.getElementsByName("level");for (var i = 0, j = radios.length; i < j; i++) {radios[i].onclick = function () {if (jms != null)if (jms.landMineCount > 0)if (!confirm("确定结束当前游戏?"))return false;var value = this.value;init(value, value, value * value / 5 - value, value * value / 5);document.getElementById("JMS_main").style.width = value * 40 + 180 + 60 + "px";}}init(10, 10);
};
function init(rowCount, colCount, minLandMineCount, maxLandMineCount) {var doc = document,landMineCountElement = doc.getElementById("landMineCount"),timeShow = doc.getElementById("costTime"),beginButton = doc.getElementById("begin");if (jms != null) {clearInterval(timeHandle);timeShow.innerHTML = 0;landMineCountElement.innerHTML = 0;}jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount);
}

然后在浏览器中打开 index.html,格子已经可以显示出来了,效果如下:

点击右边的难度选择,可以看到格子的数量变化。

游戏初始化

现在,我们开始对游戏初始化,主要分三步:

  1. 把所有格子(代码中用一个数组表示)初始化为 0

  2. 随机生成地雷个数,把地雷随机放到数组中,数组项值设置为 9

  3. 计算其他格子中的数字,值放入数组中

在 jms.js 中 JMS.prototype 内加入如下代码:

//在jms.js中JMS.prototype内加入
//初始化,一是设置数组默认值为0,二是确定地雷个数
init: function () {for (var i = 0; i < this.rowCount; i++) {this.arrs[i] = [];for (var j = 0; j < this.colCount; j++) {this.arrs[i][j] = 0;}}this.landMineCount = this.selectFrom(this.minLandMineCount, this.maxLandMineCount);this.markLandMineCount = 0;this.beginTime = null;this.endTime = null;this.currentSetpCount = 0;
},
//把是地雷的数组项的值设置为9
landMine: function () {var allCount = this.rowCount * this.colCount - 1,tempArr = {};for (var i = 0; i < this.landMineCount; i++) {var randomNum = this.selectFrom(0, allCount),rowCol = this.getRowCol(randomNum);if (randomNum in tempArr) {i--;continue;}this.arrs[rowCol.row][rowCol.col] = 9;tempArr[randomNum] = randomNum;}
},
//计算其他格子中的数字
calculateNoLandMineCount: function () {for (var i = 0; i < this.rowCount; i++) {for (var j = 0; j < this.colCount; j++) {if (this.arrs[i][j] == 9)continue;if (i > 0 && j > 0) {if (this.arrs[i - 1][j - 1] == 9)this.arrs[i][j]++;}if (i > 0) {if (this.arrs[i - 1][j] == 9)this.arrs[i][j]++;}if (i > 0 && j < this.colCount - 1) {if (this.arrs[i - 1][j + 1] == 9)this.arrs[i][j]++;}if (j > 0) {if (this.arrs[i][j - 1] == 9)this.arrs[i][j]++;}if (j < this.colCount - 1) {if (this.arrs[i][j + 1] == 9)this.arrs[i][j]++;}if (i < this.rowCount - 1 && j > 0) {if (this.arrs[i + 1][j - 1] == 9)this.arrs[i][j]++;}if (i < this.rowCount - 1) {if (this.arrs[i + 1][j] == 9)this.arrs[i][j]++;}if (i < this.rowCount - 1 && j < this.colCount - 1) {if (this.arrs[i + 1][j + 1] == 9)this.arrs[i][j]++;}}}
},
//获取一个随机数
selectFrom: function (iFirstValue, iLastValue) {var iChoices = iLastValue - iFirstValue + 1;return Math.floor(Math.random() * iChoices + iFirstValue);
},
//通过数值找到行数和列数
getRowCol: function (val) {return {row: parseInt(val / this.colCount),col: val % this.colCount};
},

给格子添加点击事件

现在,该给格子添加点击事件了,当左键点击时,显示出格子中的数字(如果是地雷就挑战失败,结束游戏),右键点击时标记为地雷。

另外,第一次点击格子(往往带有运气成分)如果周围有空白区域会直接展开。

篇幅有限,点击阅读原文,即可查看完整课程内容 + 代码  ????????????

我们还为你准备了在线的实验环境,点击一下即可开始体验这个项目。学编程,当然要边练边学了~

PC 端登陆,开始学习之旅https://www.shiyanlou.com/courses/144

前端经典练手项目|用 JavaScript 实现网页版扫雷相关推荐

  1. 熬夜整理出了70个清华大佬都在用的Python经典练手项目【附源码】

    我们都知道,不管学习那门语言最终都要做出实际的东西来,而对于编程而言,这个实际的东西当然就是项目啦,不用我多说大家都知道学编程语言做项目的重要性. 于是,小编熬了几个通宵,终于整理出了70个清华大佬都 ...

  2. 推荐 Python 十大经典练手项目,让你的 Python 技能点全亮!

    前言:如果有人问:"Python还火吗?""当然,很火.""哪能火多久呢?""不知道." 技术发展到现在衍生出许多种编程 ...

  3. python一千行入门代码-Python 有哪些一千行左右的经典练手项目?

    谢邀.据我了解,没有千行左右的「经典」练手项目.但是我可以推荐一些练手项目.这些项目来着 教你阅读Python开源项目代码 - Python之美 - 知乎专栏 : 和工作中看别人代码差不多,基本每个人 ...

  4. 推荐Python十大经典练手项目,让你的Python技能点全亮!

    前言:如果有人问:"Python还火吗?""当然,很火.""哪能火多久呢?""不知道." 技术发展到现在衍生出许多种编程 ...

  5. web前端布局练手项目

    仿北大首页布局(只关心布局) <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "htt ...

  6. 全自动配色网站。前端人员练手项目。专业配色网站

    网站首页 网站的配色模式有: 单色 互补色 三色 四色 近似色 互补近似 解决设计师的配色烦恼! 下载网址: 前端+JavaScript+配色网站源码+解决设计师的配色烦恼!全自动优雅配色,你值得拥有 ...

  7. 2022最新版40个前端练手项目【附视频+源码】

    不管学习哪门语言都希望能做出实际的东西来,这个实际的东西当然就是项目啦, 不用多 说,大家都知道学编程语言一定要做项目才行. 本次给到大家的是40个前端实战练手项目(附源码和视频讲解),希望对大家有一 ...

  8. python练手经典100例-Python 的练手项目有哪些值得推荐?

    首先两点建议:最好不要写太应用的程序练手,如果你发现你写程序的大部分时间都在查库手册(或者类似的事情),那就是大家所说的"搬砖"了:要思考什么更像是知识,什么只是经验,还是那句老话 ...

  9. Vue2实现仿小米商城练手项目前端篇(2-首页实现)

    缘由 去年寒假里学习了Vue.js,开学后想做一个完整的练手项目实战一下,最后决定模仿小米手机官网做一个网站项目,具体参考了Github上一位作者的项目. 现在已经基本完成了,分享在CSDN作为学习记 ...

  10. 5个前端练手项目(html css js canvas)

    前言: 首先祝大家端午节快乐.本篇文章有5个练手项目 对于刚学完前端三剑客的你们.应该是一个很好的实践 目录

最新文章

  1. 报告 | 超级智能城市2.0 – 人工智能引领新风尚(附下载)
  2. 如何让自己的简历在万人从中一眼就能被HR选中
  3. utc时间 单位换算_数学基础知识点总结,常用单位换算长度、时间、面积等分类...
  4. 使用eclipse 进行 Cesium 开发
  5. halcon知识:图像纹理特征提取cooc_feature_matrix
  6. 读完这些论文和代码,你就能在搜狐算法大赛中获得好成绩了
  7. C语言指令启动mcs51计时器是,嵌入式C语言程序设计:使用MCS-51
  8. pdflush内核线程池及其中隐含的竞争
  9. php formdata 多个图片保存_图片上传姿势以及你不知道的Typed Arrays
  10. LINUX其他重要服务
  11. [SVN] 分支同步、合入主干操作分享
  12. Minecraft 我的世界 .minecraft下的各个文件夹的用处
  13. 最短哈密顿路matlab,最短路径系列【最短路径、哈密顿路等】
  14. 安装NVIDIA显卡驱动以及CUDA
  15. 计算机开模拟器,iOS在同一台电脑上打开多个模拟器
  16. win10右键一直卡死解决记录
  17. 其他状态(非Buff/Debuff、异常状态的状态)
  18. python并发编程之进程1(守护进程,进程锁,进程队列)
  19. 有多少人工智能在“人工”强行“智能”
  20. 优化高手都需要深入的访问路径(ACCESS PATH)

热门文章

  1. MTK FM收音机修改门限减少杂台
  2. macBook Air出现部分乱码问题解决(浏览器、appstore乱码)
  3. 《普林斯顿微积分读本》笔记-第3章极限导论
  4. 玩转华为数据中心交换机系列 | 配置基于VLAN的MAC地址学习限制示例
  5. oracle基础语法(二)ORACLE查询
  6. 有关csdn博客账号注销说明
  7. EXCEL 数据透视表的制作
  8. java数据透视表_Java 创建 Excel 数据透视表
  9. dw2xls已升级至pb11.5
  10. 世界经典反编译工具reflector下载