用js制作一个扫雷游戏(vue版)
使用js来制作一个扫雷游戏,可以分为以下几个步骤
1、根据不同难度构建扫雷游戏区域;
2、在游戏区域中放置地雷;
3、处理点击事件;
4、处理游戏结束事件
1、根据不同难度构建扫雷游戏区域
创建一个二维数组进行扫雷区域的描述,这样就可以根据两个索引获取到每一个格子的状态描述,我给每一个格子设置了三个属性:
isMark:该格子是否已经被标记;
isOpen:该格子是否已经被点开;
value:该格子的内部的数字(‘’表示周围没有雷,1-8表示周围的雷数,9表示雷);
然后使用vue的v-for将雷区绘制出来;
2、在游戏区域中放置地雷
这部分主要思路是使用while循环,每次循环产生行和列的随机数,然后将对应行列位置的value设置为9(如果已经存在,则该次循环无效),直到将指定的地雷放置完毕;
let putedMineNum=0; //已经放置完成雷的个数
while(putedMineNum<mine){ //mine表示总共需要放置多少个雷
let putRow=Math.floor(Math.random()*row);
let putColumn=Math.floor(Math.random()*column);
let putPosition=numArr[putRow][putColumn];
//如果随机出来的位置上没有数据,放雷
if(!putPosition){
numArr[putRow][putColumn]={
value:9,
isMark:false,
isOpen:false
};
putedMineNum++;
}
}
放置地雷完成之后,就需要计算雷区的提示数字了
这部分主要思路就是遍历每一个格子,然后获取其周围格子value==9的数量,思路不难,就是遍历比较多
function _getEveryMineNum(){
let {numArr}=this;
let rowLength=numArr.length;
let columnLength=numArr[0].length;
for(let row=0;row<rowLength;row++){
for(let column=0;column<columnLength;column++){
let curContent=numArr[row][column]; //格子对象
// 如果该格子是雷块,跳过
if(curContent&&curContent.value===9){
continue;
}
//格式周围的八个格子
let posArr=[[row-1,column],[row-1,column-1],[row-1,column+1],[row,column-1],[row,column+1],[row+1,column+1],[row+1,column-1],[row+1,column]];
let mineNum=0;
for(let i=0;i<8;i++){
let _row=posArr[i][0];
let _column=posArr[i][1];
//如果周围的格子超出了整个雷区的处理
if(_row<0||_column<0||_row>numArr.length-1||_column>numArr[0].length){
continue
}
let curPos=numArr[_row][_column];
if(curPos&&curPos.value==9){
mineNum++
}
}
numArr[row][column]={
value:mineNum||'',
isMark:false,
isOpen:false
}
this.$set(this.numArr,row,numArr[row])
}
}
},
经过这两步,我们就可以获取到整个雷区的构造了。
3、处理点击事件
通过event.which可以判断鼠标是点击左键(1)还是右键(3),点击事件的规则是:
1、点击右键打上标记或者取消标记,
2、点击左键进行判断:
- 该格子是雷,游戏结束;
- 该格子是数字,相安无事;
- 该格子是空白(周围无雷)
其中当格子是空白时,会打开周围的格子,直到碰到数字格子或者游戏边界为止,如下图
function _searchMine(e,row,column){
let {numArr,isEnd}=this;
if(isEnd){
return
}
let curPos=numArr[row][column];
//点击鼠标右键
let {which}=e;
if(which==3){
//点击右键
this._rightClick(row,column);
return
}
//已被标记,不可左击
if(curPos.isMark){
return
}
if(curPos.value==9){
//点击到雷,游戏结束
this._failGame();
return
}
//点击左键
this._leftClick(row,column);
//判断是否点击完所有格子
this._isAllClick();
},
右键部分的逻辑判断很简单,只需要判断该格子的isMark状态,进行取反操作:
//点击右键
_rightClick(row,column){
let {numArr,leftMineNum,gameStep}=this;
let curBlock=numArr[row][column];
let {isMark}=curBlock;
curBlock.isMark=!isMark;
//标记数加或者减
leftMineNum=leftMineNum+(isMark?1:-1);
gameStep=gameStep+(isMark?1:-1);
this.numArr=numArr;
this.leftMineNum=leftMineNum;
this.gameStep=gameStep;
//如果已经标记完毕,立刻开始游戏结算
if(leftMineNum<=0){
this._endGame();
return
}
},
左键的逻辑如下:
//点击左键
_leftClick(row,column){
let {numArr,gameStep}=this;
let rowMax=numArr.length;
let columnMax=numArr[0].length;
//判断是否超出游戏边界
if(row<0||column<0||row>rowMax-1||column>columnMax-1){
return
}
let curPos=numArr[row][column];
//isOpen表示格子是否已经点开
if(curPos.isOpen){
return
}
curPos.isOpen=true;
//gameStep用来判断格子是否全部被点击
gameStep--;
this.gameStep=gameStep;
this.$set(this.numArr,row,numArr[row]);
//如果该格子有数字,return
if(curPos.value){
return
}
//剩下的便是格子是空白的情况,向四周进行延伸
this._leftClick(row-1,column);
this._leftClick(row+1,column);
this._leftClick(row,column-1);
this._leftClick(row,column+1);
}
4、处理游戏结束事件
游戏结束有两种情况:
1、点开+标记=== 所有格子;
2、标记数=== 雷的数量
第一种情况,我是使用了一个变量gameStep,每次gameStep的初始值等于行数(row) * 列数(column),每次打上标记或者点开格子,gameStep减1。只要判断gameStep是否小于等于0,就可以判断游戏是否结束;
//判断是否将格子全部点击完
_isAllClick(){
let {gameStep}=this;
if(gameStep>0){
return
}
this._endGame()
},
第二种情况,同样是使用了一个变量leftMineNum,每次右键时判断该变量是否小于等于0
//如果已经标记完毕,立刻开始结算
if(leftMineNum<=0){
this._endGame();
return
}
通关游戏只需要判断所有格子中雷是否已经全部被打上标记(也就是value==9&&isMark)
//进行游戏结算
_endGame(){
let {numArr}=this;
let isAllMark=numArr.every(rowItem=>{
return rowItem.every(columnItem=>{
//如果value==9必须isMark==true
return (columnItem.value==9&&columnItem.isMark)||columnItem.value!=9
})
})
if(isAllMark){
this._passGame()
}else{
this._failGame();
}
},
//游戏通关
_passGame(){
this.isEnd=true;
alert('恭喜你,游戏通关!')
},
//游戏失败,将显示出来所有的雷的位置,也就是将value==9的格子isOpen设置为true
_failGame(){
let {numArr}=this;
numArr.forEach(rowItem=>{
rowItem.forEach(columnItem=>{
let {value}=columnItem;
if(value==9){
columnItem.isOpen=true
}
})
})
this.numArr=numArr;
this.isEnd=true;
setTimeout(function(){
alert('游戏失败!')
},300)
}
注意事项
1、因为整个区域是使用二维数组进行构建的,同时又使用了vue进行数据绑定,因此在触发页面重新渲染的操作可能和一维数组不太一样,可以参考这个链接 vue中如何对多维数组进行渲染
2、vue的数据渲染是个异步操作,最后的游戏失败提示,可以使用定时器,将alert的弹出置于渲染数据完成之后;
源码
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=750, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" type="text/css" href="http://at.alicdn.com/t/font_963134_2tgi4v5uqgb.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<style type="text/css">
*{
margin:0;
padding:0;
}
.input-container{
margin:10px 0;
}
.btn{
width:60px;
line-height: 30px;
background: #3388ff;
color:#fff;
border:0 none;
margin:0 20px;
}
.type-item{
margin-bottom:10px;
display: flex;
align-items:center;
justify-content: center;
}
.type-item .item{
width:180px;
}
.left-mine{
text-align: center;
}
.wrapper{
display: flex;
justify-content: center;
}
.content{
margin-top:10px;
padding:4px;
border-radius: 4px;
background: lightblue;
}
.content .line{
margin:4px 0;
display: flex;
justify-content: center;
}
.content .line .item{
margin:0 2px;
width:20px;
height:20px;
border-radius:2px;
background: gray;
box-shadow: 0 0 4px 1px rgba(255,255,255,0.3);
cursor:pointer;
text-align: center;
font-size:12px;
color:#fff;
line-height: 20px;
transition: all 0.5s;
position:relative;
overflow: hidden;
}
.content .line .item .block{
width:100%;
height:100%;
display: block;
position:absolute;
top:0;
left:0;
text-align: center;
}
.content .line .item.active{
background: black;
}
.content .line .item.danger{
background: #fff;
color:red;
}
.content .line .item .mark{
font-size:30px;
}
</style>
</head>
<body>
<div id="container" oncontextmenu="return false">
<div class="type" v-cloak>
<div class="type-item" v-for="(item,index) in typeArr" :key="index">
<div class="item">难度{{index+1}}:{{item.row}}×{{item.column}},{{item.mine}}个雷</div>
<button class="btn" @click="_initArr(index)">选择</button>
</div>
</div>
<div class="left-mine">
<span>剩余雷数:</span>
<span>{{leftMineNum}}</span>
</div>
<div class="wrapper">
<div class="content" v-show="numArr.length">
<div class="line" v-for="(rowItem,rowIndex) in numArr" :key="rowIndex">
<div
class="item"
v-for="(columnItem,columnIndex) in rowItem"
:key="columnIndex"
@mouseDown="_searchMine($event,rowIndex,columnIndex)"
:class="{active:columnItem.isOpen&&columnItem.value!='9',danger:columnItem.isOpen&&columnItem.value=='9'}"
>
<div v-if="columnItem.isOpen" class="block">
<span class="iconfont icon-dilei" v-if='columnItem.value==9'></span>
<span v-if='columnItem.value!=9'>{{columnItem.value}}</span>
</div>
<div v-if="columnItem.isMark" class="block">
<span class="iconfont icon-qizhi"></span>
<span>×</span>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript">
let app=new Vue({
el:'#container',
data:{
typeArr:[{
row:9,
column:9,
mine:10
},{
row:16,
column:16,
mine:40
},{
row:16,
column:40,
mine:99
}
],
curType:{
row:0,
column:0,
mine:0
},
numArr:[],
leftMineNum:0,
gameStep:0,
isEnd:true
},
methods:{
_initArr(index){
this.curType=this.typeArr[index];
let {row,column,mine}=this.curType;
this.leftMineNum=mine;
this.gameStep=row*column;
this.isEnd=false;
this._createMineArea();
},
_createMineArea(){
this._putMineInArea();
this._getEveryMineNum();
},
//将雷放入区域
_putMineInArea(){
let {curType}=this;
let {row,column,mine}=curType;
let numArr=new Array(row);
for(let i=0;i<row;i++){
numArr[i]=new Array(column)
}
let putedMineNum=0;
while(putedMineNum<mine){
let putRow=Math.floor(Math.random()*row);
let putColumn=Math.floor(Math.random()*column);
let putPosition=numArr[putRow][putColumn];
if(!putPosition){
numArr[putRow][putColumn]={
value:9,
isMark:false,
isOpen:false
};
putedMineNum++;
}
}
this.numArr=numArr;
},
//获取每一个格子附近的雷数
_getEveryMineNum(){
let {numArr}=this;
let rowLength=numArr.length;
let columnLength=numArr[0].length;
for(let row=0;row<rowLength;row++){
for(let column=0;column<columnLength;column++){
let curContent=numArr[row][column];
if(curContent&&curContent.value===9){
continue;
}
let posArr=[[row-1,column],[row-1,column-1],[row-1,column+1],[row,column-1],[row,column+1],[row+1,column+1],[row+1,column-1],[row+1,column]];
let mineNum=0;
for(let i=0;i<8;i++){
let _row=posArr[i][0];
let _column=posArr[i][1];
if(_row<0||_column<0||_row>numArr.length-1||_column>numArr[0].length){
continue
}
let curPos=numArr[_row][_column];
if(curPos&&curPos.value==9){
mineNum++
}
}
numArr[row][column]={
value:mineNum||'',
isMark:false,
isOpen:false
}
this.$set(this.numArr,row,numArr[row])
}
}
console.log(numArr);
},
_searchMine(e,row,column){
let {numArr,isEnd}=this;
if(isEnd){
return
}
let curPos=numArr[row][column];
//点击鼠标右键
let {which}=e;
if(which==3){
//点击右键
this._rightClick(row,column);
return
}
//已被标记,不可左击
if(curPos.isMark){
return
}
if(curPos.value==9){
//点击到雷,游戏结束
this._failGame();
return
}
//点击左键
this._leftClick(row,column);
this._isAllClick();
},
//点击左键
_leftClick(row,column){
let {numArr,gameStep}=this;
let rowMax=numArr.length;
let columnMax=numArr[0].length;
//判断是否超出游戏边界
if(row<0||column<0||row>rowMax-1||column>columnMax-1){
return
}
let curPos=numArr[row][column];
//isOpen表示格子是否已经点开
if(curPos.isOpen){
return
}
curPos.isOpen=true;
//gameStep用来判断格子是否全部被点击
gameStep--;
this.gameStep=gameStep;
this.$set(this.numArr,row,numArr[row]);
//如果该格子有数字,return
if(curPos.value){
return
}
//剩下的便是格子是空白的情况,向四周进行延伸
this._leftClick(row-1,column);
this._leftClick(row+1,column);
this._leftClick(row,column-1);
this._leftClick(row,column+1);
},
//点击右键
_rightClick(row,column){
let {numArr,leftMineNum,gameStep}=this;
let curBlock=numArr[row][column];
let {isMark}=curBlock;
curBlock.isMark=!isMark;
leftMineNum=leftMineNum+(isMark?1:-1);
gameStep=gameStep+(isMark?1:-1);
this.numArr=numArr;
this.leftMineNum=leftMineNum;
this.gameStep=gameStep;
//如果已经标记完毕,立刻开始结算
if(leftMineNum<=0){
this._endGame();
return
}
},
//判断是否将格子全部点击完
_isAllClick(){
let {gameStep}=this;
if(gameStep>0){
return
}
this._endGame()
},
//进行游戏结算
_endGame(){
let {numArr}=this;
let isAllMark=numArr.every(rowItem=>{
return rowItem.every(columnItem=>{
return (columnItem.value==9&&columnItem.isMark)||columnItem.value!=9
})
})
if(isAllMark){
this._passGame()
}else{
this._failGame();
}
},
//游戏通关
_passGame(){
this.isEnd=true;
alert('恭喜你,游戏通关!')
},
//结束游戏
_failGame(){
let {numArr}=this;
numArr.forEach(rowItem=>{
rowItem.forEach(columnItem=>{
let {value,isOpen,isMark}=columnItem;
if(value==9){
columnItem.isOpen=true
}
})
})
this.numArr=numArr;
this.isEnd=true;
setTimeout(function(){
alert('游戏失败!')
},300)
}
}
})
</script>
</html>