├── .gitattributes ├── boom.svg ├── flag.svg ├── img ├── demo1.gif ├── demo2.png ├── demo3.png ├── demo4.jpg └── demo4.png ├── index.html ├── jin.css ├── jin.js └── readme.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /boom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 12 | 19 | 24 | 41 | 46 | 47 | -------------------------------------------------------------------------------- /flag.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /img/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijinyc/game-mineSweepinng/065bfaab8f86098a2db8c3321b0524197b596687/img/demo1.gif -------------------------------------------------------------------------------- /img/demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijinyc/game-mineSweepinng/065bfaab8f86098a2db8c3321b0524197b596687/img/demo2.png -------------------------------------------------------------------------------- /img/demo3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijinyc/game-mineSweepinng/065bfaab8f86098a2db8c3321b0524197b596687/img/demo3.png -------------------------------------------------------------------------------- /img/demo4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijinyc/game-mineSweepinng/065bfaab8f86098a2db8c3321b0524197b596687/img/demo4.jpg -------------------------------------------------------------------------------- /img/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caijinyc/game-mineSweepinng/065bfaab8f86098a2db8c3321b0524197b596687/img/demo4.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 |
18 |
19 |

20 | 剩余雷数 : 21 | 22 |

23 |

24 | TIME : 25 | S 26 |

27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /jin.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | position: relative; 4 | } 5 | 6 | .level { 7 | margin-top: 30px; 8 | font-size: 18px; 9 | } 10 | 11 | .level button { 12 | padding: 3px 8px; 13 | background: rgb(67, 183, 189); 14 | /* border: 1px solid rgb(129, 129, 129); */ 15 | border: none; 16 | color: white; 17 | border-radius: 3px; 18 | outline: none; 19 | cursor:pointer; 20 | } 21 | .level button:hover { 22 | background: rgb(23, 132, 138); 23 | } 24 | .row { 25 | margin: 0; 26 | padding: 0; 27 | overflow: hidden; 28 | letter-spacing: -8px; 29 | } 30 | .col { 31 | display: inline-block; 32 | background: rgba(32, 226, 255, 0.41); 33 | border: 1px solid rgb(129, 129, 129); 34 | margin: 1.5px; 35 | width: 23px; 36 | height: 23px; 37 | letter-spacing: normal; 38 | position: relative; 39 | } 40 | 41 | .col span { 42 | display: inline-block; 43 | position: relative; 44 | top: 2px; 45 | opacity: 0; 46 | font-weight: bold; 47 | /* 标准箭头 */ 48 | cursor:default; 49 | } 50 | 51 | .col:hover { 52 | background: blue; 53 | } 54 | 55 | .gameBox { 56 | margin-top: 30px; 57 | } 58 | 59 | .img-flag { 60 | position: absolute; 61 | top: 3px; 62 | left: 3.5px; 63 | width: 18px; 64 | height: 18px; 65 | } 66 | 67 | .hide { 68 | display: none; 69 | } 70 | 71 | .boom { 72 | background: url('boom.svg') no-repeat 2.5px 2px; 73 | background-size: 18px 18px; 74 | } 75 | 76 | .div-boom { 77 | font-size: 30px; 78 | position: fixed; 79 | top: 50px; 80 | left: 50%; 81 | } 82 | 83 | .info { 84 | margin-top: 30px; 85 | } 86 | .info p { 87 | display: inline-block; 88 | width: 130px; 89 | margin: 0 10px; 90 | } 91 | .info p span { 92 | color: rgb(67, 183, 189); 93 | } 94 | .num-1 { 95 | color: rgb(8, 153, 235); 96 | } 97 | .num-2 { 98 | color: rgb(255, 45, 178); 99 | } 100 | .num-3 { 101 | color: rgb(109, 224, 176); 102 | } 103 | .num-4 { 104 | color: rgb(8, 153, 235); 105 | } 106 | .num-5 { 107 | color: rgb(255, 167, 45); 108 | } 109 | .num-6 { 110 | color: rgb(49, 140, 102); 111 | } 112 | .num-7 { 113 | color: rgb(168, 55, 237); 114 | } 115 | 116 | .num-8 { 117 | color: rgb(15, 254, 154); 118 | } 119 | -------------------------------------------------------------------------------- /jin.js: -------------------------------------------------------------------------------- 1 | /* 2 | 扫雷游戏: 3 | 4 | 初级:10个 9*9 5 | 中级:40个 16*16 6 | 高级:99个 28*28 7 | 8 | 第一步: 9 | 输出一个 10 | 11 | [ 12 | [1, 9, 2, 1], 13 | [2, 4, 9, 2], 14 | [9, 4, 9, 2], 15 | [2, 9, 2, 1], 16 | ] 17 | 18 | 这样的数组,其中需要的数值有:1,雷的数量 2,行数和列数 19 | 20 | 实现步骤: 21 | 1,根据行数和列数创建一个多维数组(使用 for 循环嵌套实现) 22 | 2,然后使用 Math 随机 a[x][x] 来写入雷的位置(再次使用 for 循环,写入 1) 23 | 如果位置已经有雷了就重写随机然后写入 24 | 3,bibibibibi --> 实现上述的数组 25 | 过程:1,先生成一行 26 | 2,复制成列 27 | 3.塞入雷 28 | 4.写一个函数实现:如果数组位置上的值为 1 的话,就给边上一圈的值加 1, 29 | 30 | 第二步: 31 | 进行操作: 32 | 当点击到 0 的值的时候, 33 | 开始遍历边上一圈的值(打开所有边上的值),如果值也是 0 的话,就接着遍历边上一圈的值,直到没有为止。 34 | 这里可以写一个函数,当点击到 0 的时候就用这个函数,然后给边上还是 0 的继续使用这个函数 35 | */ 36 | 37 | // 1,成一张雷的地图 38 | var mineSweepingMap = function (r, c, num) { 39 | var map = [] 40 | // 给行数,生成一个 1 维数组 41 | var row = function (r) { 42 | for (var i = 0; i < r; i++) { 43 | map[i] = new Array() 44 | } 45 | } 46 | // 给列数,生成一个 2 维数组 47 | var column = function (col) { 48 | for (var i = 0; i < map.length; i++) { 49 | for (var j = 0; j < col; j++) { 50 | map[i][j] = 0 51 | } 52 | } 53 | } 54 | // 给列数和行数生成空地图 55 | var blankMap = function (r, col) { 56 | row(r) 57 | column(col) 58 | } 59 | 60 | // 给出地雷数量让后随机写入地雷 61 | var writeInMine = function (num) { 62 | // 随机位置写入 63 | var randomLocation = function () { 64 | var x = Math.floor(Math.random() * r) 65 | var y = Math.floor(Math.random() * c) 66 | // console.log( ':', x, y); 67 | if (map[x][y] !== 9) { 68 | map[x][y] = 9 69 | } else { 70 | randomLocation() 71 | } 72 | } 73 | for (var i = 0; i < num; i++) { 74 | randomLocation() 75 | } 76 | } 77 | 78 | // 使用循环给雷的边上所有数 +1 , 已经是雷的除外 79 | var plus = function (array, x, y) { 80 | if (x >= 0 && x < r && y >= 0 && y < c) { 81 | if (array[x][y] !== 9) { 82 | array[x][y] += 1 83 | } 84 | } 85 | } 86 | var writeInHint = function () { 87 | for (var x = 0; x < map.length; x++) { 88 | for (var y = 0; y < map[0].length; y++) { 89 | if (map[x][y] === 9) { 90 | // 上下 6 个 91 | for (var i = -1; i < 2; i++) { 92 | plus(map, x - 1, y + i) 93 | plus(map, x + 1, y + i) 94 | } 95 | // 左右 2 个 96 | plus(map, x, y + 1) 97 | plus(map, x, y - 1) 98 | } 99 | } 100 | } 101 | } 102 | 103 | blankMap(r, c) 104 | writeInMine(num) 105 | writeInHint() 106 | return map 107 | } 108 | 109 | // 2,将雷写入页面 110 | var writeHtml = function (map) { 111 | // 先通过 y轴数量写入 ul,然后通过 x轴上的数量写入 li 112 | var x = document.querySelector('.gameBox') 113 | for (var i = 0; i < map.length; i++) { 114 | x.innerHTML = x.innerHTML + `` 115 | } 116 | 117 | var z = document.querySelectorAll('.row') 118 | for (var i = 0; i < z.length; i++) { 119 | for (var j = 0; j < map[0].length; j++) { 120 | var m = map[i][j] 121 | if (m === 0) { 122 | m = '' 123 | } 124 | z[i].innerHTML = z[i].innerHTML + ` 125 |
  • 126 | ${m} 127 | 128 |
  • ` 129 | } 130 | } 131 | } 132 | 133 | // 判断是否胜利 134 | var changeClearMineNum = function (clearMineNum) { 135 | // console.log('zmzmzmzm'); 136 | // console.log('zz', zz); 137 | if (clearMineNum === ((col * row) - num)) { 138 | var all = document.querySelectorAll('.col') 139 | var allNum = 0 140 | var stop = setInterval(function () { 141 | var r = Math.floor(Math.random() * 256) 142 | var g = Math.floor(Math.random() * 256) 143 | var b = 210 144 | // var b = Math.floor(Math.random() * 256) 145 | all[allNum].children[0].style.opacity = `0` 146 | all[allNum].children[1].style.opacity = '0' 147 | all[allNum].style.background = `rgba(${r},${g},${b},0.6)` 148 | allNum++ 149 | if (allNum === all.length) { 150 | clearInterval(stop) 151 | if (zz === 0) { 152 | alert('你成功啦~!!晚上吃肉~~!') 153 | initializeGame(row, col, num) 154 | } 155 | initializeGame(row, col, num) 156 | } 157 | }, 20) 158 | } 159 | } 160 | 161 | // 3,扫雷过程 162 | var clearMine = function (row, col, num) { 163 | var clearMineNum = 0 164 | var makeWhite = function (x, y) { 165 | if (x < row && y < col && x >= 0 && y >= 0) { 166 | var el = document.querySelector(`.x-${x}`).children[y] 167 | // 需要注意这个 !== 'white' ,如果不加这个就会进入无限循环 168 | if (el.style.background !== 'white') { 169 | el.style.background = 'white' 170 | el.children[0].style.opacity = '1' 171 | el.children[1].classList.add('hide') 172 | clearMineNum++ 173 | // console.log(clearMineNum, 'clearMineNum'); 174 | changeClearMineNum(clearMineNum) 175 | if (el.innerText === '') { 176 | showNoMine(x, y) 177 | } 178 | } 179 | } 180 | } 181 | // 智能扫雷 182 | var showNoMine = function (x, y) { 183 | // console.log(x, y, 'x,y'); 184 | makeWhite(x - 1, y + 1) 185 | makeWhite(x - 1, y - 1) 186 | makeWhite(x - 1, y) 187 | makeWhite(x + 1, y + 1) 188 | makeWhite(x + 1, y - 1) 189 | makeWhite(x + 1, y) 190 | makeWhite(x, y + 1) 191 | makeWhite(x, y - 1) 192 | } 193 | 194 | // 给所有方块绑定点击事件,点击显示数字,或者 boom 195 | var show = function () { 196 | // var x = document.querySelectorAll('.col') 197 | var x = document.querySelectorAll('.row') 198 | for (var i = 0; i < x.length; i++) { 199 | x[i].addEventListener('click', function (event) { 200 | var el = event.target 201 | if (el.tagName != 'LI') { 202 | // 因为事件委托的原因 203 | // 如果点击到了 span 上面,那么就会出现 bug 204 | // 所以如果点击到 span 上面,那么 el 就等于 span 的父节点 205 | el = event.target.parentElement 206 | } 207 | // 已经被标记的不能点击 208 | var flag = el.children[1].classList.contains('hide') 209 | if (el.tagName === 'LI' && flag) { 210 | if (el.children[0].innerText !== '9' && el.style.background !== 'white') { 211 | el.children[0].style.opacity = '1' 212 | el.style.background = 'white' 213 | clearMineNum++ 214 | changeClearMineNum(clearMineNum) 215 | // console.log(clearMineNum, 'clearMineNum'); 216 | } else if (el.children[0].innerText === '9') { 217 | // el.children[0].style.opacity = '1' 218 | zz = 1 219 | el.classList.add('boom') 220 | alert('游戏失败') 221 | var all = document.querySelectorAll('.col') 222 | var ff = [] 223 | var allNum = 0 224 | // 这里做了个小动画,失败的时候慢慢的显示雷的位置 225 | for (var i = 0; i < all.length; i++) { 226 | if (all[i].children[0].innerText === '9') { 227 | // all[i].style.background = 'red' 228 | ff[allNum] = all[i] 229 | allNum++ 230 | } 231 | } 232 | allNum = 0 233 | var time = 60 234 | if (num > 50) { 235 | time = 10 236 | } else if (num > 10) { 237 | time = 25 238 | } 239 | var stop = setInterval(function () { 240 | ff[allNum].classList.add('boom') 241 | allNum++ 242 | if (allNum === ff.length) { 243 | clearInterval(stop) 244 | // console.log('stop'); 245 | } 246 | }, time) 247 | // var box = document.querySelector('.gameBox') 248 | // box.innerHTML = '' 249 | // var level = event.target.innerHTML 250 | // var body = document.querySelector('body') 251 | // initializeGame(row, col, num) 252 | } 253 | // 如果点击的方格为空(什么有没有),那么周围没有点开的空方格都会被点开 254 | if (el.children[0].innerText === '') { 255 | // 获取到位置 256 | var x = parseInt(el.parentElement.dataset.x) 257 | var y = parseInt(el.dataset.y) 258 | // console.log(x,y, 'data'); 259 | // 背景变成白色 260 | showNoMine(x, y) 261 | } 262 | } 263 | }) 264 | } 265 | for (var i = 0; i < x.length; i++) { 266 | var mineNum = num 267 | x[i].addEventListener('contextmenu', function (event) { 268 | event.preventDefault(); 269 | var btnNum = event.button 270 | var el = event.target 271 | if (el.tagName != 'LI') { 272 | // 因为事件委托的原因 273 | // 如果点击到了 span 上面,那么就会出现 bug 274 | // 所以如果点击到 span 上面,那么 el 就等于 span 的父节点 275 | el = event.target.parentElement 276 | } 277 | if (el.tagName === 'LI') { 278 | var classList = el.children[1].classList 279 | // 已经被点击过的地方不能标记 280 | if (classList.contains('hide') && el.style.background !== 'white') { 281 | var residue = document.querySelector('.residue') 282 | if (mineNum !== 0) { 283 | mineNum-- 284 | } 285 | residue.innerText = `${mineNum}` 286 | classList.remove('hide') 287 | } else if (el.style.background !== 'white') { 288 | classList.add('hide') 289 | } 290 | } 291 | }) 292 | } 293 | } 294 | show() 295 | } 296 | 297 | // 4,清除画面,然后写入新的画面 298 | var stopTime 299 | var initializeGame = function (row, col, num) { 300 | var residue = document.querySelector('.residue') 301 | residue.innerText = `${num}` 302 | var time = document.querySelector('.tick') 303 | time.innerText = `0` 304 | var i = 1 305 | clearInterval(stopTime) 306 | stopTime = setInterval(function () { 307 | time.innerText = `${i++}` 308 | }, 1000) 309 | // zz 310 | zz = 0 311 | // 首先清除原来的地图,然后重新初始化 312 | var box = document.querySelector('.gameBox') 313 | box.innerHTML = '' 314 | var body = document.querySelector('body') 315 | body.style.minWidth = `${27 * col}px` 316 | var map = mineSweepingMap(row, col, num) 317 | writeHtml(map) 318 | clearMine(row, col, num) 319 | } 320 | 321 | // 5,选择游戏等级,给按钮绑定功能 322 | var Btn = function () { 323 | var level = document.querySelectorAll('.choice-level') 324 | for (var i = 0; i < level.length; i++) { 325 | level[i].addEventListener('click', function (event) { 326 | var level = event.target.innerHTML 327 | if (level === '初级') { 328 | row = 9 329 | col = 9 330 | num = 10 331 | initializeGame(row, col, num) 332 | } else if (level === '中级') { 333 | row = 16 334 | col = 16 335 | num = 40 336 | initializeGame(row, col, num) 337 | } else if (level === '高级') { 338 | row = 16 339 | col = 30 340 | num = 99 341 | initializeGame(row, col, num) 342 | } 343 | }) 344 | } 345 | var restart = document.querySelector('.restart') 346 | restart.addEventListener('click', function (event) { 347 | initializeGame(row, col, num) 348 | }) 349 | } 350 | Btn() 351 | 352 | // 6,初始数据 353 | // zz 用来确定是否已经点到地雷 354 | var zz = 0 355 | var row = 16 356 | var col = 16 357 | var num = 40 358 | initializeGame(row, col, num) 359 | 360 | // 给一个坐标,把四周变成白色 361 | 362 | // 根据 363 | // 绑定鼠标右击事件,右击鼠标的时候进行标记, 364 | // 这个时候要进行 css 的变化 365 | // 当所有地雷被标记,或者说所有数组中只剩 9,游戏成功。 366 | 367 | // 选择游戏难度 368 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # 原生 JavaScript 实现扫雷游戏 2 | 3 | 学习了一段时间之后,使用原始 JS 实现的一个扫雷游戏,未使用 canvas,所以性能方面有些问题。 4 | 5 | 如果感兴趣的话可以试试:[Demo](http://caijin.tech/demo/mineSweeping/index.html) 6 | 7 | ## 效果图 8 | ![demo1](https://github.com/CaiJinyc/game-mineSweepinng/blob/master/img/demo1.gif)![demo2](https://github.com/CaiJinyc/game-mineSweepinng/blob/master/img/demo2.png)![demo3](https://github.com/CaiJinyc/game-mineSweepinng/blob/master/img/demo3.png)![demo4](https://github.com/CaiJinyc/game-mineSweepinng/blob/master/img/demo4.png) 9 | 10 | ## 功能 11 | #### 实现的功能 12 | 基本扫雷的功能都实现了,例如: 13 | * 计时 14 | * 选择游戏难度 15 | * 标记地雷(插旗子标记地雷,标记之后不能点击) 16 | * 剩余雷数(总的雷数减去插旗的数量) 17 | * 自动连锁点开(当点开某个区块后,如果该区块的数字为 0,也就是九宫格内没有雷,那么将自动点开九宫格内的所有区块) 18 | 19 | 还做了点小彩蛋,例如:踩到地雷时,地雷会逐步显示,还有成功扫到所有雷之后,地图逐渐被彩色方块覆盖,然后提示扫雷成功。 20 | 21 | #### 没有实现的功能 22 | 自定义,问号标记(偷懒了偷懒了,说不定以后会补上呢 *(鬼才会信吧)*)。 23 | 24 | #### 生成一张扫雷地图 25 | 这里当然用的是数组啦,会玩扫雷的应该都懂,如果一个方块块有雷,那么边上的值都加 1(就是根据这个扫的嘛~没有这个还怎么玩)。我相信很多人是不会像看代码的,所以我直接讲我的思路。 26 | 27 | 1. 根据行数和列数创建一个多维数组(使用 for 循环嵌套实现) 28 | 2. 然后使用 Math 随机 map[x][x] 来写入雷的位置(再次使用 for 循环,写入 9(9 就代表雷)),如果位置已经有雷了就重写随机然后写入 29 | 3. 然后我们就会得到一个这样的数组,这个时候我们只需要让 9 的四周加上 1 30 | [ [0, 9, 0, 0], 31 | [0, 0, 9, 0], 32 | [9, 0, 9, 0], 33 | [0, 9, 0, 0] ] 34 | 4. 得到这样的数组,这样就大功告成啦。 35 | [ [1, 9, 2, 1], 36 | [2, 4, 9, 2], 37 | [9, 4, 9, 2], 38 | [2, 9, 2, 1] ] 39 | 40 | 41 | #### 将地图写入页面 42 | 使用 doucument.querySelector 获取到元素节点,然后使用 innerHTML 就行了。(感觉自己说这两句像是在放屁)。想了解的直接去 Github 看源码把,一看就懂。 43 | 44 | #### 自动连锁点开 45 | 这个比较难,想了挺久的。思路大概是这样: 46 | 1. 点击到为 0 的位置,就自动显示周围一圈的位置。 47 | 2. 然后周围一圈的还有为 0 的位置,就继续显示周围一圈,然后循环到没有为止。 48 | --------------------------------------------------------------------------------