前端|HTML 实现扫雷游戏

HTML 实现扫雷游戏

文章目录

      • HTML 实现扫雷游戏
        • 项目原理:
        • 总结

项目原理: 扫雷游戏的规则很简单:
游戏面板上有一些格子,每个格子中有一个数字(空白表示数字为 0)或是地雷,格子中的数字表示格子周围格子中地雷的数量。玩家要做的就是把数字格子找出来,时间花的越少越好。
除边界上的格子外,每个格子周围有 8 个格子:上、下、左、右、4 个斜角。所以数字范围是 0~8。
所以我们的算法如下:
根据用户选择的难易程度(有初、中、高三个级别,级别越高地雷和格子数量越多),随机产生一定个数的地雷并随机放在格子中。然后遍历格子,计算每个格子中的数字,标记在格子上。玩家左键点击格子时显示格子内容(如果遇到地雷则挑战失败,游戏结束),右键点击格子时标记格子为地雷,真到正确标记所有地雷并打开所有非地雷格子,挑战成功,游戏结束。
小技巧:由于格子中数字范围是 0~8,所以为了便于计算,我们可以把地雷所在的格子中的数字记为 9
项目的目录结构:
minesweeper |__index.html |__index.css |__index.js |__jms.js

图片下载地址:
https://labfile.oss.aliyuncs.com/courses/144/mine.png https://labfile.oss.aliyuncs.com/courses/144/flag.png

下面将从页面布局开始,一步一步完成所有代码的编写
首先我们需要有一个面板来显示游戏信息,包括剩余地雷个数、所用时间、难度级别等。因为格子数量不是固定的,所以我们先不画格子,放在 JS 代码中绘制。
创建 index.html 文件,添加如下代码并保存:
JavaScript版扫雷 - 锐客网
剩余雷数:class="light red" id="landMineCount">0 个
持续时间: class="light f60" id="costTime">0 秒
难度选择:



提示:
  • 1、点击“开始游戏”游戏开始计时
  • 2、游戏过程中点击“开始游戏”将开始新游戏
src="https://www.it610.com/article/jms.js"> src="https://www.it610.com/article/index.js">

然后需要调整面板上游戏信息的位置,添加一些样式。在 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; }

完成这步后,浏览器打开看看
index.html
效果如下:
前端|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(''); for (var j = 0; j < this.colCount; j++) { tds.push(""); } tds.push(''); } this.setTableInnerHTML(this.table, tds.join('')); }, //添加HTML到Table setTableInnerHTML: function (table, html) { if (navigator && navigator.userAgent.match(/msie/i)) { // 在 table 的 owner document 内创建 div var temp = table.ownerDocument.createElement('div'); // 创建表格的 tbody 内容 temp.innerHTML = '' + html + '
'; 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.html,格子已经可以显示出来了,效果如下:
前端|HTML 实现扫雷游戏
文章图片

点击右边的难度选择,可以看到格子的数量变化。
现在,我们开始对游戏初始化,主要分三步:
  1. 把所有格子(代码中用一个数组表示)初始化为 0;
  2. 随机生成地雷个数,把地雷随机放到数组中,数组项值设置为 9;
  3. 计算其他格子中的数字,值放入数组中。
jms.jsJMS.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 }; },

给格子添加点击事件
现在,该给格子添加点击事件了,当左键点击时,显示出格子中的数字(如果是地雷就挑战失败,结束游戏),右键点击时标记为地雷。
另外,第一次点击格子(往往带有运气成分)如果周围有空白区域会直接展开。
jms.js 中的这部分代码如下:
//在jms.js中JMS.prototype内加入 //获取元素 $: function (id) { return this.doc.getElementById(id); }, //给每个格子绑定点击事件(左键和右键) bindCells: function () { var self = this; for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { (function (row, col) { self.$("m_" + i + "_" + j).onmousedown = function (e) { e = e || window.event; var mouseNum = e.button; var className = this.className; if (mouseNum == 2) { if (className == "flag") { this.className = ""; self.markLandMineCount--; } else { this.className = "flag"; self.markLandMineCount++; } if (self.landMineCallBack) { self.landMineCallBack(self.landMineCount - self.markLandMineCount); } } else if (className != "flag") { self.openBlock.call(self, this, row, col); } }; })(i,j); } } }, //展开无雷区域 showNoLandMine: function (x, y) { for (var i = x - 1; i < x + 2; i++) for (var j = y - 1; j < y + 2; j++) { if (!(i == x && j == y)) { var ele = this.$("m_" + i + "_" + j); if (ele && ele.className == "") { this.openBlock.call(this, ele, i, j); } } } }, //显示 openBlock: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; if (this.arrs[x][y] != 0) { obj.innerHTML = this.arrs[x][y]; } obj.className = "normal"; if (this.currentSetpCount + this.landMineCount == this.rowCount * this.colCount) { this.success(); } obj.onmousedown = null; if (this.arrs[x][y] == 0) { this.showNoLandMine.call(this, x, y); } } else { this.failed(); } }, //显示地雷 showLandMine: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) { this.$("m_" + i + "_" + j).className = "landMine"; } } } }, //显示所有格子信息 showAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) { this.$("m_" + i + "_" + j).className = "landMine"; } else { var ele=this.$("m_" + i + "_" + j); if (this.arrs[i][j] != 0) ele.innerHTML = this.arrs[i][j]; ele.className = "normal"; } } } }, //清除显示的格子信息 hideAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { var tdCell = this.$("m_" + i + "_" + j); tdCell.className = ""; tdCell.innerHTML = ""; } } }, //删除格子绑定的事件 disableAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { var tdCell = this.$("m_" + i + "_" + j); tdCell.onmousedown = null; } } },

添加游戏控制功能
到现在为止,游戏主要部分已经完成,下面要做的就是添加游戏控制功能,让游戏正常运行起来,主要有以下几步:
  1. 给游戏开始按钮添加点击事件,重置游戏参数;
  2. 游戏开始后开始记时;
  3. 游戏结束就停止记时并提示。
jms.js 中添加游戏入口和开始功能:
//在 jms.js 中 JMS.prototype 内加入 //游戏开始 begin: function () { this.currentSetpCount = 0; //开始的步数清零 this.markLandMineCount = 0; this.beginTime = new Date(); //游戏开始时间 this.hideAll(); this.bindCells(); }, //游戏结束 end: function () { this.endTime = new Date(); //游戏结束时间 if (this.endCallBack) {//如果有回调函数则调用 this.endCallBack(); } }, //游戏成功 success: function () { this.end(); this.showAll(); this.disableAll(); alert("Congratulation!"); }, //游戏失败 failed: function () { this.end(); this.showAll(); this.disableAll(); alert("GAME OVER!"); }, //入口 play: function () { this.init(); this.landMine(); this.calculateNoLandMineCount(); },

index.js 中给开始按钮添加事件,开始游戏,并显示游戏时间和剩余地雷个数:
//在index.js的init中,jms = JMS("landmine", rowCount, colCount, minLandMineCount, maxLandMineCount); 之后加入 jms.endCallBack = function () { clearInterval(timeHandle); }; jms.landMineCallBack = function (count) { landMineCountElement.innerHTML = count; }; //为“开始游戏”按钮绑定事件 beginButton.onclick = function () { jms.play(); //初始化//显示地雷个数 landMineCountElement.innerHTML = jms.landMineCount; //开始 jms.begin(); //更新花费的时间 timeHandle = setInterval(function () { timeShow.innerHTML = parseInt((new Date() - jms.beginTime) / 1000); }, 1000); };

jms.js 完整代码
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.prototype = { //画格子 drawMap: function () { var tds = []; if ( window.ActiveXObject && parseInt(navigator.userAgent.match(/msie ([\d.]+)/i)[1]) < 8 ) { var css = '#JMS_main table td{background-color:#888; }', head = this.doc.getElementsByTagName('head')[0], style = this.doc.createElement('style'); style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(this.doc.createTextNode(css)); } head.appendChild(style); } for (var i = 0; i < this.rowCount; i++) { tds.push(''); for (var j = 0; j < this.colCount; j++) { tds.push(""); } tds.push(''); } this.setTableInnerHTML(this.table, tds.join('')); }, //添加HTML到Table setTableInnerHTML: function (table, html) { if (navigator && navigator.userAgent.match(/msie/i)) { var temp = table.ownerDocument.createElement('div'); temp.innerHTML = '' + html + '
'; 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; } }, //初始化,一是设置数组默认值为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, }; }, //获取元素 $: function (id) { return this.doc.getElementById(id); }, //给每个格子绑定点击事件(左键和右键) bindCells: function () { var self = this; for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { (function (row, col) { self.$('m_' + i + '_' + j).onmousedown = function (e) { e = e || window.event; var mouseNum = e.button; var className = this.className; if (mouseNum == 2) { if (className == 'flag') { this.className = ''; self.markLandMineCount--; } else { this.className = 'flag'; self.markLandMineCount++; } if (self.landMineCallBack) { self.landMineCallBack( self.landMineCount - self.markLandMineCount ); } } else if (className != 'flag') { self.openBlock.call(self, this, row, col); } }; })(i, j); } } }, //展开无雷区域 showNoLandMine: function (x, y) { for (var i = x - 1; i < x + 2; i++) for (var j = y - 1; j < y + 2; j++) { if (!(i == x && j == y)) { var ele = this.$('m_' + i + '_' + j); if (ele && ele.className == '') { this.openBlock.call(this, ele, i, j); } } } }, //显示 openBlock: function (obj, x, y) { if (this.arrs[x][y] != 9) { this.currentSetpCount++; if (this.arrs[x][y] != 0) { obj.innerHTML = this.arrs[x][y]; } obj.className = 'normal'; if ( this.currentSetpCount + this.landMineCount == this.rowCount * this.colCount ) { this.success(); } obj.onmousedown = null; if (this.arrs[x][y] == 0) { this.showNoLandMine.call(this, x, y); } } else { this.failed(); } }, //显示地雷 showLandMine: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) { this.$('m_' + i + '_' + j).className = 'landMine'; } } } }, //显示所有格子信息 showAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { if (this.arrs[i][j] == 9) { this.$('m_' + i + '_' + j).className = 'landMine'; } else { var ele = this.$('m_' + i + '_' + j); if (this.arrs[i][j] != 0) ele.innerHTML = this.arrs[i][j]; ele.className = 'normal'; } } } }, //清除显示的格子信息 hideAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { var tdCell = this.$('m_' + i + '_' + j); tdCell.className = ''; tdCell.innerHTML = ''; } } }, //删除格子绑定的事件 disableAll: function () { for (var i = 0; i < this.rowCount; i++) { for (var j = 0; j < this.colCount; j++) { var tdCell = this.$('m_' + i + '_' + j); tdCell.onmousedown = null; } } }, //游戏开始 begin: function () { this.currentSetpCount = 0; //开始的步数清零 this.markLandMineCount = 0; this.beginTime = new Date(); //游戏开始时间 this.hideAll(); this.bindCells(); }, //游戏结束 end: function () { this.endTime = new Date(); //游戏结束时间 if (this.endCallBack) { //如果有回调函数则调用 this.endCallBack(); } }, //游戏成功 success: function () { this.end(); this.showAll(); this.disableAll(); alert('Congratulation!'); }, //游戏失败 failed: function () { this.end(); this.showAll(); this.disableAll(); alert('GAME OVER!'); }, //入口 play: function () { this.init(); this.landMine(); this.calculateNoLandMineCount(); }, }; window.JMS = JMS; })();

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 = https://www.it610.com/article/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); jms.endCallBack = function () { clearInterval(timeHandle); }; jms.landMineCallBack = function (count) { landMineCountElement.innerHTML = count; }; //为“开始游戏”按钮绑定事件 beginButton.onclick = function () { jms.play(); //初始化//显示地雷个数 landMineCountElement.innerHTML = jms.landMineCount; //开始 jms.begin(); //更新花费的时间 timeHandle = setInterval(function () { timeShow.innerHTML = parseInt((new Date() - jms.beginTime) / 1000); }, 1000); }; }

到此我们的网页版扫雷就完成了。打开 index.html 文件,就可以开始游戏了。
前端|HTML 实现扫雷游戏
文章图片

总结 【前端|HTML 实现扫雷游戏】主要使用 JavaScript 实现了网页版的经典小游戏扫雷。相信通过本项目,可以提高大家对 JavaScript 的理解与运用能力。此外,大家也可以学习到如何使用 JavaScript 语言来抽象、封装游戏中的对象
源码:
https://download.csdn.net/download/Deng872347348/85301530

    推荐阅读