Javascript 实现扫雷小游戏(含详细注释)

ゝ一世哀愁。 2022-11-27 08:50 390阅读 0赞

" class="reference-link">样例展示在这里插入图片描述

在这里插入图片描述

文件夹结构

在这里插入图片描述

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>JS扫雷</title>
  7. <link rel="stylesheet" href="CSS/index.css">
  8. <link rel="icon" href="favicon.ico">
  9. </head>
  10. <body>
  11. <div id="mine">
  12. <div class="level">
  13. <button class="active">初级</button>
  14. <button>中级</button>
  15. <button>高级</button>
  16. <button>重新开始</button>
  17. </div>
  18. <div class="gameBox">
  19. </div>
  20. <div class="info">
  21. 剩余雷数:<span class="mineNum"></span>
  22. <br>
  23. <span class="tips">左键扫雷,右键插旗,再次点击右键拔旗</span>
  24. </div>
  25. </div>
  26. <script src="JS/index.js"></script>
  27. </body>
  28. </html>

index.css

  1. #mine {
  2. margin: 50px auto;
  3. }
  4. .level {
  5. text-align: center;
  6. margin-bottom: 10px;
  7. }
  8. .level button {
  9. padding: 5px 15px;
  10. background-color: #02a4ad;
  11. border: none;
  12. color: #fff;
  13. border-radius: 3px;
  14. outline: none;
  15. cursor: pointer;
  16. }
  17. .level button.active {
  18. background-color: #00abff;
  19. }
  20. table {
  21. border-spacing: 1px;
  22. background-color: #929196;
  23. margin: 0 auto;
  24. }
  25. td {
  26. padding: 0;
  27. width: 20px;
  28. height: 20px;
  29. background-color: #ccc;
  30. border: 2px solid;
  31. border-color: #fff #a1a1a1 #a1a1a1 #fff;
  32. text-align: center;
  33. line-height: 20px;
  34. font-weight: bold;
  35. }
  36. .tips {
  37. color: red;
  38. font-size: 16px;
  39. }
  40. .mine {
  41. background: #d9d9d9 url(../images/mine.png) no-repeat center;
  42. background-size: cover;
  43. }
  44. .flag {
  45. background: #ccc url(../images/flag.png) no-repeat center;
  46. background-size: cover;
  47. }
  48. .info {
  49. margin-top: 10px;
  50. text-align: center;
  51. }
  52. td.zero {
  53. background-color: #d9d9d9;
  54. border-color: #d9d9d9;
  55. }
  56. td.one {
  57. background-color: #d9d9d9;
  58. border-color: #d9d9d9;
  59. color: #0332fe;
  60. }
  61. td.two {
  62. background-color: #d9d9d9;
  63. border-color: #d9d9d9;
  64. color: #019f02;
  65. }
  66. td.three {
  67. background-color: #d9d9d9;
  68. border-color: #d9d9d9;
  69. color: #ff2600;
  70. }
  71. td.four {
  72. background-color: #d9d9d9;
  73. border-color: #d9d9d9;
  74. color: #93208f;
  75. }
  76. td.five {
  77. background-color: #d9d9d9;
  78. border-color: #d9d9d9;
  79. color: #ff7f29;
  80. }
  81. td.six {
  82. background-color: #d9d9d9;
  83. border-color: #d9d9d9;
  84. color: #ff3fff;
  85. }
  86. td.seven {
  87. background-color: #d9d9d9;
  88. border-color: #d9d9d9;
  89. color: #3fffbf;
  90. }
  91. td.eight {
  92. background-color: #d9d9d9;
  93. border-color: #d9d9d9;
  94. color: #22ee0f;
  95. }

index.js(核心代码)

内含详细注释

  1. function Mine(tr, td, mineNum) {
  2. this.tr = tr; // tr表示行数
  3. this.td = td; // td表示列数
  4. this.mineNum = mineNum; // mineNum表示雷的数量
  5. this.squares = []; // 存储所有方块的信息,是一个二维数组,按照行与列的顺序排放,存取都按照行列方式
  6. this.tds = []; // 存储所有单元格的DOM
  7. this.surplusMine = mineNum; // 剩余雷的数量
  8. this.allRight = false; // 右击标注的小红旗是否全部是雷,用来判断用户是否游戏成功
  9. this.parent = document.querySelector('.gameBox');
  10. }
  11. // 生成n个不重复的数字
  12. Mine.prototype.randomNum = function () {
  13. var square = new Array(this.tr * this.td); // 生成一个空数组 长度为格子总数
  14. for (var i = 0; i < square.length; i++) {
  15. square[i] = i;
  16. }
  17. // 数组乱序
  18. square.sort(function () {
  19. return 0.5 - Math.random()
  20. });
  21. return square.slice(0, this.mineNum);
  22. };
  23. Mine.prototype.init = function () {
  24. // this.randomNum();
  25. var rn = this.randomNum(); // 雷在格子里的位置
  26. var n = -1; // 用来找到对应的索引格子
  27. for (var i = 0; i < this.tr; i++) {
  28. this.squares[i] = [];
  29. for (var j = 0; j < this.td; j++) {
  30. // 取一个方块在数组里的数据,要使用行与列的形式存取
  31. // 找方块周围的方块的时候,要用坐标的形式去取
  32. // 行与列的形式,和坐标的形式,x,y是刚好相反的
  33. n++;
  34. if (rn.indexOf(n) != -1) {
  35. // 如果这个条件成立,说明现在循环到的索引在雷的数组里面找到了,那就表明这个索引对应的是个雷
  36. this.squares[i][j] = {
  37. type: 'mine',
  38. x: j,
  39. y: i
  40. };
  41. } else {
  42. this.squares[i][j] = {
  43. type: 'number',
  44. x: j,
  45. y: i,
  46. value: 0
  47. };
  48. }
  49. }
  50. }
  51. this.updateNum();
  52. this.createDom();
  53. this.parent.oncontextmenu = function () {
  54. return false;
  55. // 阻止右键出菜单事件
  56. }
  57. // 剩余雷数
  58. this.mineNumDom = document.querySelector('.mineNum');
  59. this.mineNumDom.innerHTML = this.surplusMine;
  60. };
  61. // 创建表格
  62. Mine.prototype.createDom = function () {
  63. var This = this;
  64. var table = document.createElement('table');
  65. for (var i = 0; i < this.tr; i++) {
  66. // 行
  67. var domTr = document.createElement('tr');
  68. this.tds[i] = [];
  69. for (var j = 0; j < this.td; j++) {
  70. // 列
  71. var domTd = document.createElement('td');
  72. this.tds[i][j] = domTd; // 把所有创建的td添加到数组当中
  73. domTd.pos = [i, j]; // 把格子对应的行和列村到格子身上,为了下面通过这个值去数组里面取到对应的数据
  74. domTd.onmousedown = function () {
  75. This.play(event, this); // 大的This 指的是实例对象 小的this指的是点击的domTd
  76. };
  77. // if (this.squares[i][j].type == 'mine') {
  78. // domTd.className = 'mine';
  79. // }
  80. // if (this.squares[i][j].type == 'number') {
  81. // domTd.innerHTML = this.squares[i][j].value;
  82. // }
  83. domTr.appendChild(domTd);
  84. }
  85. table.appendChild(domTr);
  86. }
  87. this.parent.innerHTML = ''; // 避免多次点击创建多个
  88. this.parent.appendChild(table);
  89. };
  90. // 找某个方格周围的八个格子
  91. Mine.prototype.getAround = function (square) {
  92. var x = square.x,
  93. y = square.y;
  94. var result = []; // 把找到的格子的坐标返回出去(二维数组)
  95. for (var i = x - 1; i <= x + 1; i++) {
  96. for (var j = y - 1; j <= y + 1; j++) {
  97. if (i < 0 ||
  98. j < 0 ||
  99. i > this.td - 1 ||
  100. j > this.tr - 1 ||
  101. // 上述表示出边界
  102. (i == x && j == y) ||
  103. // 表示循环到自己
  104. this.squares[j][i].type == 'mine'
  105. // 表示循环到(周围的格子)雷 (注意i和j表示的是坐标,而squares存储的是行和列)
  106. ) {
  107. continue;
  108. }
  109. // 要以行与列的形式返回出去,因为到时候需要它去取数组里的数据
  110. result.push([j, i]);
  111. }
  112. }
  113. return result;
  114. }
  115. // 更新所有的数字
  116. Mine.prototype.updateNum = function () {
  117. for (var i = 0; i < this.tr; i++) {
  118. for (var j = 0; j < this.td; j++) {
  119. // 要更新的是雷周围的数字
  120. if (this.squares[i][j].type == 'number') {
  121. continue;
  122. }
  123. var num = this.getAround(this.squares[i][j]); // 获取到每一个雷周围的数字
  124. for (var k = 0; k < num.length; k++) {
  125. this.squares[num[k][0]][num[k][1]].value += 1;
  126. }
  127. }
  128. }
  129. };
  130. Mine.prototype.play = function (ev, obj) {
  131. var This = this;
  132. if (ev.which == 1 && obj.className != 'flag') {
  133. // 后面的条件是为了用户右键之后不能点击
  134. // 点击的是左键
  135. var curSquare = this.squares[obj.pos[0]][obj.pos[1]];
  136. var cl = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight'];
  137. // cl 存储className
  138. if (curSquare.type == 'number') {
  139. // 用户点击的是数字
  140. obj.innerHTML = curSquare.value;
  141. obj.className = cl[curSquare.value];
  142. // 点到了数字零
  143. if (curSquare.value == 0) {
  144. /*
  145. 递归思路:
  146. 1.显示自己
  147. 2.查找四周
  148. 1) 显示四周(如果四周的值不为零,那就显示到这,不需要再找了)
  149. 2)如果值为零了
  150. a.显示自己
  151. b.找四周(如果四周的值不为零,那就显示到这,不需要再找了)
  152. I.显示自己
  153. II.找四周(如果四周的值不为零,那就显示到这,不需要再找了)
  154. 。。。。。。
  155. */
  156. obj.innerHTML = ''; // 显示为空
  157. function getAllZero(square) {
  158. var around = This.getAround(square); // 找到了周围N个格子
  159. for (var i = 0; i < around.length; i++) {
  160. var x = around[i][0]; // 行
  161. var y = around[i][1]; // 列
  162. This.tds[x][y].className = cl[This.squares[x][y].value];
  163. if (This.squares[x][y].value == 0) {
  164. // 如果以某个格子为中心,找到的某个格子为零,那就接着调用(递归)
  165. if (!This.tds[x][y].check) {
  166. // 给对应的td 添加一条属性,如果找过的话,这个值就为true,下一次就不会再找了,防止函数调用栈出问题
  167. This.tds[x][y].check = true;
  168. getAllZero(This.squares[x][y]);
  169. }
  170. } else {
  171. // 如果以某个格子为中心找到的四周的值不为零,就把数字显示出来
  172. This.tds[x][y].innerHTML = This.squares[x][y].value;
  173. }
  174. }
  175. }
  176. getAllZero(curSquare);
  177. }
  178. } else {
  179. // 用户点击的是雷
  180. this.gameOver(obj);
  181. }
  182. }
  183. if (ev.which == 3) {
  184. // 用户点击的是右键
  185. // 如果右击的是一个数字,就不能点击
  186. if (obj.className && obj.className != 'flag') {
  187. return;
  188. }
  189. obj.className = obj.className == 'flag' ? '' : 'flag'; // 切换calss 有无
  190. if (this.squares[obj.pos[0]][obj.pos[1]].type == 'mine') {
  191. this.allRight = true;
  192. } else {
  193. this.allRight = false;
  194. }
  195. if (obj.className == 'flag') {
  196. this.mineNumDom.innerHTML = --this.surplusMine;
  197. } else {
  198. this.mineNumDom.innerHTML = ++this.surplusMine;
  199. }
  200. if (this.surplusMine == 0) {
  201. // 剩余的雷的数量为0,表示用户已经标完小红旗了,这时候要判断游戏是成功还是结束
  202. if (this.allRight == true) {
  203. // 这个条件成立,说明用户全部标对了
  204. alert('恭喜你,游戏通过');
  205. for (i = 0; i < this.tr; i++) {
  206. for (j = 0; j < this.td; j++) {
  207. this.tds[i][j].onmousedown = null;
  208. }
  209. }
  210. } else {
  211. alert('游戏失败');
  212. this.gameOver();
  213. }
  214. }
  215. }
  216. }
  217. // 游戏失败函数
  218. Mine.prototype.gameOver = function (clickTd) {
  219. /*
  220. 1.显示所有的雷
  221. 2.取消所有格子的点击事件
  222. 3.给点中的格子标红
  223. */
  224. for (i = 0; i < this.tr; i++) {
  225. for (j = 0; j < this.td; j++) {
  226. if (this.squares[i][j].type == 'mine') {
  227. this.tds[i][j].className = 'mine';
  228. }
  229. this.tds[i][j].onmousedown = null;
  230. }
  231. }
  232. if (clickTd) {
  233. clickTd.style.backgroundColor = '#f00';
  234. }
  235. }
  236. // var mine = new Mine(28, 28, 99);
  237. // mine.init();
  238. // 添加 button 的功能
  239. var btns = document.getElementsByTagName('button');
  240. var mine = null; // 用来存储生成的实例
  241. var ln = 0; // 用来处理当前选中的状态
  242. var arr = [
  243. [9, 9, 10],
  244. [16, 16, 40],
  245. [28, 28, 99]
  246. ]; //不同级别的行数,列数,雷数
  247. for (let i = 0; i < btns.length - 1; i++) {
  248. btns[i].onclick = function () {
  249. btns[ln].className = '';
  250. this.className = 'active';
  251. mine = new Mine(arr[i][0], arr[i][1], arr[i][2]);
  252. mine.init();
  253. ln = i;
  254. }
  255. }
  256. btns[0].onclick(); // 初始化
  257. btns[3].onclick = function () {
  258. for (var i = 0; i < btns.length - 1; i++) {
  259. if (btns[i].className == 'active') {
  260. btns[i].onclick();
  261. }
  262. }
  263. }

图片

在这里插入图片描述
在这里插入图片描述
其中的 favicon.ico 是用地雷进行格式转换得到的,大家可自行解决

end

发表评论

表情:
评论列表 (有 0 条评论,390人围观)

还没有评论,来说两句吧...

相关阅读

    相关 扫雷游戏

    先感谢大家的反馈意见,扫雷小游戏的升级版已经完成。至此,扫雷游戏开发也就告一段落了。当前的版本在上一版本的基础上做了如下修改: 1. 雷区方格加大了5个像素点; 2. 资源