├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------