闲话不多说,先上效果图
实现的功能
点击控制后按钮可以生成对应难度的雷盘 鼠标左右键点击每个格子可显示里面的数字或做标记 当点击出的数字为 0 时,通过扩散算法让这个格子周围为 0 的格子显示出来 随机雷的位置时不能将雷的信息保存在元素的类名上,防止用户通过检查元素类名来找到雷。 游戏结束时弹出当前游戏的时间。
主要思路:
由于有多个游戏难度供用户选择,所以采用面向对象的方式进行编程。 生成雷盘
根据用户选择的难度通过循环雷盘行与列的方式来创建相应数量的表格,并以一个二维数组的格式好保存每个格子的 DOM 对象,然后将元素插入到页面中
constructor ( tr, td, mineNum ) {
this . tr = tr;
this . td = td;
this . mineNum = mineNum;
this . squares = [ ] ;
this . allDom = [ ] ;
this . surplusMine = mineNum;
this . allIn = false ;
this . parent = document. getElementsByClassName ( 'gameBox' ) [ 0 ] ;
this . mineNumDom = document. getElementsByClassName ( 'mineNum' ) [ 0 ] ;
}
creatDom ( ) {
let table = document. createElement ( 'table' ) ;
for ( let i = 0 ; i < this . tr; i++ ) {
let trDom = document. createElement ( 'tr' ) ;
this . allDom[ i] = [ ] ;
for ( let j = 0 ; j < this . td; j++ ) {
let tdDom = document. createElement ( 'td' ) ;
tdDom. onmousedown = ( e ) => {
this . play ( e) ;
}
tdDom. pos = [ i, j] ;
this . allDom[ i] [ j] = tdDom;
trDom. appendChild ( tdDom) ;
}
table. appendChild ( trDom) ;
}
this . parent. innerHTML = '' ;
this . parent. appendChild ( table) ;
}
生成随机数,并将每个方块的信息保存到二维数组中
把雷盘每个格子的索引添加到一个数组中并截取出前 n 个数字进行乱序。
randomNum ( ) {
let arr = new Array ( this . tr * this . td) ;
for ( let i = arr. length; i > 0 ; i-- ) {
arr[ i] = i
}
return arr. sort ( ( ) => 0.5 - Math. random ( ) ) . slice ( 0 , this . mineNum) ;
}
以行与列的形式遍历,将雷的信息以对象的形式保存到一个二维数组中,类似于: [
[
{type: 'number', x: 1, y: 7, value: 0 }
{type: 'number', x: 13, y: 4, value: 1 }
{type: 'number', x: 3, y: 12, value: 4 }
{type: 'number', x: 4, y: 14, value: 6 }
....
],
[
{type: 'number', x: 10, y: 4, value: 1 }
{type: 'number', x: 2, y: 7, value: 2}
{type: 'number', x: 11, y: 9, value: 6 }
{type: 'number', x: 5, y: 2, value: 7 }
......
],
[.......],
......
]
里面一层的所有[]可以理解成一个列,[]里的每个对象可以理解为每列里有n个,展开后就是一个阵列。
saveSquaresInfo ( ) {
let randomArr = this . randomNum ( ) ;
let n = 0 ;
for ( let i = 0 ; i < this . tr; i++ ) {
this . squares[ i] = [ ] ;
for ( let j = 0 ; j < this . td; j++ ) {
n++ ;
if ( randomArr. indexOf ( n) == - 1 ) {
this . squares[ i] [ j] = { type: 'number' , x: j, y: i, value: 0 }
} else {
this . squares[ i] [ j] = { type: 'mine' , x: j, y: i }
}
}
}
}
找某个格子的四周格子的坐标
首先必须要知道一个格子的坐标,然后通过以下的规律遍历九宫格,剔除周围非数字的格子、越界、传入的格子本身和雷的情况后,就可以将得到的坐标以数组的形式保存起来。 找格子的规律
x-1, y-1 x, y-1 x+1, y-1
x-1, y x, y x+1, y
x-1, y+1 x, y+1 x+1, y+1
由于这种规律遍历是通过像素坐标的方式进行的,为了使保存的具有一致性,就得让得到的像素的 x,y 的顺序对调。
getAround ( square ) {
let x = square. x;
let y = square. y;
let result = [ ] ;
for ( let i = x - 1 ; i <= x + 1 ; i++ ) {
for ( let j = y - 1 ; j <= y + 1 ; j++ ) {
if (
i < 0 ||
j < 0 ||
i > this . td - 1 ||
j > this . tr - 1 ||
( i == x && j == y) ||
this . squares[ j] [ i] . type == 'mine'
) {
continue ;
}
result. push ( [ j, i] ) ;
}
}
return result;
}
更新每个雷周围的数字
通过遍历雷盘里所有的格子来找到是雷的格子的坐标,然后将雷的坐标依次调用找四周的方法来找到周围是数字的格子,并让里面的数字++。
updataNum ( ) {
for ( let i = 0 ; i < this . tr; i++ ) {
for ( let j = 0 ; j < this . td; j++ ) {
if ( this . squares[ i] [ j] . type == 'number' ) {
continue ;
}
let num = this . getAround ( this . squares[ i] [ j] ) ;
for ( let k = 0 ; k < num. length; k++ ) {
this . squares[ num[ k] [ 0 ] ] [ num[ k] [ 1 ] ] . value += 1 ;
}
}
}
}
扩散算法
接收一个格子的坐标和保存有颜色类名的数组 将接收到的坐标先调用找格子四周的方法,找到每个数字不为0的格子,让这个格子的数字和颜色显示在页面中。如果找到的是数字为0的格子,就要把找过的格子做个标记,将周围剩下的所有没有标记过的数字为格子的坐标,依次传入扩散算法中递归。
diffusion ( square, clArr ) {
let around = this . getAround ( square) ;
for ( let i = 0 ; i < around. length; i++ ) {
let x = around[ i] [ 0 ] ;
let y = around[ i] [ 1 ] ;
this . allDom[ x] [ y] . className = clArr[ this . squares[ x] [ y] . value] ;
if ( this . squares[ x] [ y] . value == 0 ) {
if ( ! this . allDom[ x] [ y] . check) {
this . allDom[ x] [ y] . check = true ;
this . diffusion ( this . squares[ x] [ y] , clArr) ;
}
} else {
this . allDom[ x] [ y] . innerHTML = this . squares[ x] [ y] . value;
}
}
}
给雷盘绑定鼠标事件
将事件源身上触发的事件冒泡到雷盘元素上执行。 左键点击
点击到数字
非0数字:显示数字,并给一个class类名以展示字体颜色和背景色
let currDom = event. target;
if ( event. button == 0 && currDom. className != 'flag' ) {
let currSquare = this . squares[ currDom. pos[ 0 ] ] [ currDom. pos[ 1 ] ] ;
let colorArr = [ 'zero' , 'one' , 'two' , 'three' , 'four' , 'five' , 'six' , 'seven' , 'eight' ]
if ( currSquare. type == 'number' ) {
currDom. innerHTML = currSquare. value;
currDom. className = colorArr[ currSquare. value] ;
}
- 数字0:不显示数字值,给一个class类名以展示背景色,并去调用扩散算法,让当前被点击到的格子,显示周围数字为0的格子,实现那种一点一大片的操作。
if ( currSquare. value == 0 ) {
currDom. innerHTML = '' ;
this . diffusion ( currSquare, colorArr) ;
}
- 点击到雷
- 调用游戏结束的方法。
if ( ! currDom. className) {
currDom. className = 'flag' ;
this . mineNumDom. innerHTML = -- this . surplusMine;
} else if ( currDom. className == 'flag' ) {
currDom. className = '' ;
this . mineNumDom. innerHTML = ++ this . surplusMine;
}
将所有小红旗都标完后,只要有一个雷没有标中,就去调用游戏结束方法,否则就是游戏通过。
if ( this . squares[ currDom. pos[ 0 ] ] [ currDom. pos[ 1 ] ] . type == 'mine' ) {
this . allIn = true ;
} else {
this . allIn = false ;
}
if ( this . surplusMine == 0 ) {
if ( this . allIn) {
alert ( '恭喜你,游戏过关!' + '所用时间为:' + this . getGameTime ( ) ) ;
} else {
this . over ( ) ;
}
}
if ( dom) {
dom. style. backgroundColor = '#ff0' ;
}
for ( let i = 0 ; i < this . tr; i++ ) {
for ( let j = 0 ; j < this . td; j++ ) {
if ( this . squares[ i] [ j] . type == 'mine' ) {
this . allDom[ i] [ j] . className = 'mine' ;
}
}
}
this . allDom[ i] [ j] . onmousedown = null ;
获取游戏时长
主要是将毫秒格式的时间转换为00:00:00格式的时间。 时间超过3600秒时,最左边的两个0是十进制的数,其余的都是60进制的数,然后摩除再拼接就得到了,最后记得更新上次保留的时间。
getGameTime ( ) {
lastTime = new Date ( ) . getTime ( ) ;
let time = ( lastTime - newTime) / 1000 ;
let str, hours, minute, second;
if ( time > 3600 ) {
let num = parseInt ( parseInt ( time / 3600 ) ) ;
hours = num >= 10 ? num + ':' : '0' + num + ':' ;
minute = ( time - num * 3600 ) / 60 >= 10 ? parseInt ( ( time - num * 3600 ) / 60 ) + ':' : '0' + parseInt ( ( time - num * 3600 ) / 60 ) + ':' ;
} else {
hours = parseInt ( time / 3600 ) > 0 ? ( parseInt ( time / 3600 ) >= 10 ? ( parseInt ( time / 3600 ) + ':' ) : ( '0' + parseInt ( time / 3600 ) + ':' ) ) : '' ;
minute = parseInt ( time / 60 ) >= 10 ? ( parseInt ( time % 3600 ) >= 59 ? ( parseInt ( parseInt ( time % 3600 ) / 60 ) + ':' ) : ( parseInt ( time / 60 ) + ':' ) ) : ( '0' + parseInt ( time / 60 ) + ':' ) ;
}
second = parseInt ( time % 60 ) >= 10 ? parseInt ( time % 60 ) : '0' + parseInt ( time % 60 ) ;
newTime = lastTime;
return str = hours + minute + second;
}
游戏难度按钮相关
给按钮绑定事件,当按钮被点击就可以根据索引拿到相对应的雷盘的数据并生成,并做到按钮的切换。 当点击的是重新开始可直接初始化一个新的同等难度等级的雷盘。
oBtnArr. forEach ( function ( oBtn, index ) {
oBtn. onclick = function ( ) {
if ( index == 3 ) {
mine = new Mine ( ... arr[ lastActiveIndex] ) ;
mine. init ( ) ;
} else {
oBtnArr[ lastActiveIndex] . className = '' ;
oBtnArr[ index] . className = 'active' ;
mine = new Mine ( ... arr[ index] ) ;
mine. init ( ) ;
lastActiveIndex = index;
}
}
} )
项目完整代码
html
<! DOCTYPE html >
< html lang = " en" >
< head>
< meta charset = " UTF-8" >
< meta http-equiv = " X-UA-Compatible" content = " IE=edge" >
< meta name = " viewport" content = " width=device-width, initial-scale=1.0" >
< title> 扫雷游戏</ title>
< link rel = " stylesheet" href = " ./index.css" >
</ head>
< body>
< div id = " mine" >
< div class = " level" >
< button class = " active" > 初级</ button>
< button> 中级</ button>
< button> 高级</ button>
< button> 重新开始</ button>
</ div>
< div class = " gameBox" > </ div>
< div class = " info" >
剩余雷数: < sapn class = " mineNum" > </ sapn>
</ div>
</ div>
</ body>
< script src = " ./index.js" > </ script>
</ html>
css
* {
padding : 0px;
margin : 0px;
list-style : none;
}
#mine {
margin : 50px auto;
}
.level {
text-align : center;
margin-bottom : 10px;
}
.level button {
padding : 5px 15px;
border : none;
color : #fff;
background-color : #02a4ad;
border-radius : 3px;
outline : none;
cursor : pointer;
}
.level button.active {
background-color : #00abff;
}
table {
border-spacing : 1px;
background-color : #929196;
margin : 20px auto;
}
td {
width : 20px;
height : 20px;
padding : 0px;
border : 2px solid;
border-color : #fff #a1a1a1 #a1a1a1 #fff;
background-color : #ccc;
text-align : center;
line-height : 20px;
font-weight : bold;
font-family : '宋体' ;
}
td.zero {
background : #d9d9d9;
border-color : #d9d9d9;
}
td.one {
background : #d9d9d9;
border-color : #d9d9d9;
color : #0332fe;
}
td.two {
background : #d9d9d9;
border-color : #d9d9d9;
color : #019f02;
}
td.three {
background : #d9d9d9;
border-color : #d9d9d9;
color : #ff2600;
}
td.four {
background : #d9d9d9;
border-color : #d9d9d9;
color : #93208f;
}
td.five {
background : #d9d9d9;
border-color : #d9d9d9;
color : #ff7f29;
}
td.six {
background : #d9d9d9;
border-color : #d9d9d9;
color : #ff3fff;
}
td.seven {
background : #d9d9d9;
border-color : #d9d9d9;
color : #3fffbf;
}
td.eight {
background : #d9d9d9;
border-color : #d9d9d9;
color : #22ee0f;
}
.gameBox ::selection {
background : none;
}
.info {
margin-top : 20px;
text-align : center;
}
.info div {
margin-top : 15px;
}
.mine {
background : #d9d9d9 url ( ./pic/mine.png) no-repeat center;
background-size : cover;
}
.flag {
background : #ccc url ( ./pic/flag.png) no-repeat center;
background-size : cover;
}
class Mine {
constructor ( tr, td, mineNum ) {
this . tr = tr;
this . td = td;
this . mineNum = mineNum;
this . squares = [ ] ;
this . allDom = [ ] ;
this . surplusMine = mineNum;
this . allIn = false ;
this . parent = document. getElementsByClassName ( 'gameBox' ) [ 0 ] ;
this . mineNumDom = document. getElementsByClassName ( 'mineNum' ) [ 0 ] ;
}
init ( ) {
this . saveSquaresInfo ( ) ;
this . updataNum ( ) ;
this . creatDom ( ) ;
this . parent. oncontextmenu = ( ) => false ;
this . mineNumDom. innerHTML = this . mineNum;
}
creatDom ( ) {
let table = document. createElement ( 'table' ) ;
for ( let i = 0 ; i < this . tr; i++ ) {
let trDom = document. createElement ( 'tr' ) ;
this . allDom[ i] = [ ] ;
for ( let j = 0 ; j < this . td; j++ ) {
let tdDom = document. createElement ( 'td' ) ;
tdDom. onmousedown = ( e ) => {
this . play ( e) ;
}
tdDom. pos = [ i, j] ;
this . allDom[ i] [ j] = tdDom;
trDom. appendChild ( tdDom) ;
}
table. appendChild ( trDom) ;
}
this . parent. innerHTML = '' ;
this . parent. appendChild ( table) ;
}
randomNum ( ) {
let arr = new Array ( this . tr * this . td) ;
for ( let i = arr. length; i > 0 ; i-- ) {
arr[ i] = i
}
return arr. sort ( ( ) => 0.5 - Math. random ( ) ) . slice ( 0 , this . mineNum) ;
}
saveSquaresInfo ( ) {
let randomArr = this . randomNum ( ) ;
let n = 0 ;
for ( let i = 0 ; i < this . tr; i++ ) {
this . squares[ i] = [ ] ;
for ( let j = 0 ; j < this . td; j++ ) {
n++ ;
if ( randomArr. indexOf ( n) == - 1 ) {
this . squares[ i] [ j] = { type: 'number' , x: j, y: i, value: 0 }
} else {
this . squares[ i] [ j] = { type: 'mine' , x: j, y: i }
}
}
}
}
getAround ( square ) {
let x = square. x;
let y = square. y;
let result = [ ] ;
for ( let i = x - 1 ; i <= x + 1 ; i++ ) {
for ( let j = y - 1 ; j <= y + 1 ; j++ ) {
if (
i < 0 ||
j < 0 ||
i > this . td - 1 ||
j > this . tr - 1 ||
( i == x && j == y) ||
this . squares[ j] [ i] . type == 'mine'
) {
continue ;
}
result. push ( [ j, i] ) ;
}
}
return result;
}
updataNum ( ) {
for ( let i = 0 ; i < this . tr; i++ ) {
for ( let j = 0 ; j < this . td; j++ ) {
if ( this . squares[ i] [ j] . type == 'number' ) {
continue ;
}
let num = this . getAround ( this . squares[ i] [ j] ) ;
for ( let k = 0 ; k < num. length; k++ ) {
this . squares[ num[ k] [ 0 ] ] [ num[ k] [ 1 ] ] . value += 1 ;
}
}
}
}
diffusion ( square, clArr ) {
let around = this . getAround ( square) ;
for ( let i = 0 ; i < around. length; i++ ) {
let x = around[ i] [ 0 ] ;
let y = around[ i] [ 1 ] ;
this . allDom[ x] [ y] . className = clArr[ this . squares[ x] [ y] . value] ;
if ( this . squares[ x] [ y] . value == 0 ) {
if ( ! this . allDom[ x] [ y] . check) {
this . allDom[ x] [ y] . check = true ;
this . diffusion ( this . squares[ x] [ y] , clArr) ;
}
} else {
this . allDom[ x] [ y] . innerHTML = this . squares[ x] [ y] . value;
}
}
}
play ( event ) {
let currDom = event. target;
if ( event. button == 0 && currDom. className != 'flag' ) {
let currSquare = this . squares[ currDom. pos[ 0 ] ] [ currDom. pos[ 1 ] ] ;
let colorArr = [ 'zero' , 'one' , 'two' , 'three' , 'four' , 'five' , 'six' , 'seven' , 'eight' ]
if ( currSquare. type == 'number' ) {
currDom. innerHTML = currSquare. value;
currDom. className = colorArr[ currSquare. value] ;
if ( currSquare. value == 0 ) {
currDom. innerHTML = '' ;
this . diffusion ( currSquare, colorArr) ;
}
} else if ( currSquare. type == 'mine' ) {
this . over ( currDom) ;
}
} else if ( event. button == 2 ) {
if ( ! currDom. className) {
currDom. className = 'flag' ;
this . mineNumDom. innerHTML = -- this . surplusMine;
} else if ( currDom. className == 'flag' ) {
currDom. className = '' ;
this . mineNumDom. innerHTML = ++ this . surplusMine;
}
if ( this . squares[ currDom. pos[ 0 ] ] [ currDom. pos[ 1 ] ] . type == 'mine' ) {
this . allIn = true ;
} else {
this . allIn = false ;
}
if ( this . surplusMine == 0 ) {
if ( this . allIn) {
alert ( '恭喜你,游戏过关!' + '所用时间为:' + this . getGameTime ( ) ) ;
} else {
this . over ( ) ;
}
}
}
}
over ( dom ) {
for ( let i = 0 ; i < this . tr; i++ ) {
for ( let j = 0 ; j < this . td; j++ ) {
if ( this . squares[ i] [ j] . type == 'mine' ) {
this . allDom[ i] [ j] . className = 'mine' ;
}
this . allDom[ i] [ j] . onmousedown = null ;
}
}
if ( dom) {
dom. style. backgroundColor = '#ff0' ;
}
alert ( '游戏结束!' + '所用时间为:' + this . getGameTime ( ) ) ;
}
getGameTime ( ) {
lastTime = new Date ( ) . getTime ( ) ;
let time = ( lastTime - newTime) / 1000 ;
let str, hours, minute, second;
if ( time > 3600 ) {
let num = parseInt ( parseInt ( time / 3600 ) ) ;
hours = num >= 10 ? num + ':' : '0' + num + ':' ;
minute = ( time - num * 3600 ) / 60 >= 10 ? parseInt ( ( time - num * 3600 ) / 60 ) + ':' : '0' + parseInt ( ( time - num * 3600 ) / 60 ) + ':' ;
} else {
hours = parseInt ( time / 3600 ) > 0 ? ( parseInt ( time / 3600 ) >= 10 ? ( parseInt ( time / 3600 ) + ':' ) : ( '0' + parseInt ( time / 3600 ) + ':' ) ) : '' ;
minute = parseInt ( time / 60 ) >= 10 ? ( parseInt ( time % 3600 ) >= 59 ? ( parseInt ( parseInt ( time % 3600 ) / 60 ) + ':' ) : ( parseInt ( time / 60 ) + ':' ) ) : ( '0' + parseInt ( time / 60 ) + ':' ) ;
}
second = parseInt ( time % 60 ) >= 10 ? parseInt ( time % 60 ) : '0' + parseInt ( time % 60 ) ;
newTime = lastTime;
return str = hours + minute + second;
}
}
let oBtnArr = [ ... document. getElementsByTagName ( 'button' ) ] ;
let arr = [ [ 15 , 15 , 15 ] , [ 20 , 25 , 50 ] , [ 25 , 35 , 175 ] ] ;
let lastActiveIndex = 0 ;
let mine;
let newTime;
let lastTime;
oBtnArr. forEach ( function ( oBtn, index ) {
oBtn. onclick = function ( ) {
if ( index == 3 ) {
mine = new Mine ( ... arr[ lastActiveIndex] ) ;
mine. init ( ) ;
} else {
oBtnArr[ lastActiveIndex] . className = '' ;
oBtnArr[ index] . className = 'active' ;
mine = new Mine ( ... arr[ index] ) ;
mine. init ( ) ;
lastActiveIndex = index;
}
}
} )
oBtnArr[ 0 ] . onclick ( ) ;
newTime = new Date ( ) . getTime ( ) ;
原创不易,各位看官老爷三连可好。🍻🍻🍻