需求背景
扫雷游戏作为小时候入手电脑的入门级别游戏,其中有很多的编程知识可以学习得到。本文整理了一个完整的基于vue
的小程序端扫雷游戏。我们可以从实现扫雷规则的过程中锻炼到vue
的各类语法操作以及前端的样式调整,以及最常用的各类排版布局!😄
一、效果预览
二、技术关键点
2.1 扫雷和排雷
在程序中我们通过随机生成的二维数组生成了动态雷区分布图。然后我们就需要用户进行扫雷和排雷操作:
- 其中,扫雷操作是用户通过
@tap
绑定点击事件,将特定二维坐标的样式进行转化:如果判断是雷则直接显示地雷图标,如果是数字则正常显示。 - 排雷操作我们让用户通过
@longpress
,该方法允许用户通过长按的动作进行触发。
三、完整源码
<template> <view class="content"> <view style=" background-image: url('https://img1.baidu.com/it/u=2339750922,2310796253&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=500'); background-repeat: no-repeat; background-position: 60% 200%; width: 100%; height: 100vh; position: absolute; z-index: -1; "></view> <view class="titleLine"> <view @tap="initMap"> <image src="./imgs/dead.png" v-if="isGameOver"></image> <image src="./imgs/smile.png" v-else-if="isGameSuccess"></image> <image src="./imgs/smile2.png" v-else></image> </view> <view style="font-weight: bold;color: #fff;">剩余:{{getRestBoomNum()}}</view> </view> <view class="contentMap"> <view style="width: auto; height: auto; overflow: scroll;"> <view class="placeInRow" v-for="(row,i) in mask" :key="'row-'+i"> <view class="content" v-for="(block,j) in row" :key="'block-'+j"> <view v-if="block === 1" class="block"> <view v-if="maps[i][j] > 0" @tap="setMask(i,j,'open')">{{maps[i][j]}}</view> <view v-else-if="maps[i][j] === 0"></view> <view v-else> <image src="./imgs/boom.png"></image> </view> </view> <view v-else-if="block === 0" class="block mask" @tap="setMask(i,j,'open')" @longpress="setMask(i,j,'mask')" ></view> <view v-else-if="block === -1" class="block mask" @longpress="setMask(i,j,'mask')"> <image src="./imgs/flag.png"></image> </view> <view v-else-if="block === 2" class="block mask"> <image src="./imgs/error.png"></image> </view> </view> </view> </view> </view> </view> </template> <script> /** * @property {Number} width 扫雷地图宽 * @property {Number} height 扫雷地图高 * @property {Number} boomNum 雷个数 * @property {Function} @init 地图初始化监听,返回游戏地图:-1表示雷,0-9表示周围有几个雷 * @property {Function} @result 游戏结束监听, code=0成功,其余失败 * */ export default { props:{ width:{ type:Number, default:8 }, height:{ type:Number, default:8 }, boomNum:{ type:Number, default:10, } }, watch:{ width(newVal){ this.initMap() }, height(newVal){ this.initMap() }, boomNum(newVal){ this.initMap() } }, data() { return { maps:[], mask:[], booms:[], isGameOver:false, isGameSuccess:false, lastAction:'', }; }, mounted() { this.initMap() }, methods:{ getRestBoomNum(){ try{ var maskNum = 0; var shownNum = 0; for (var i=0;i<this.width;i++){ for (var j=0;j<this.height;j++){ if(this.mask[i][j] == -1) maskNum ++; if(this.mask[i][j] == 1) shownNum ++; } } // console.log(shownNum, this.booms.length) this.$nextTick(function(){ if(shownNum + this.booms.length == this.width*this.height && !this.isGameSuccess){ this.isGameSuccess = true; for (var i=0;i<this.width;i++){ for (var j=0;j<this.height;j++){ if(this.mask[i][j] == 0 && this.maps[i][j] == -1) this.mask[i][j] = -1 } } this.$forceUpdate() this.$emit('result', {code:0, msg:'success'}) } }) return this.booms.length - maskNum; } catch(e){ return this.boomNum; } }, initMap(){ this.maps = [] this.mask = [] this.isGameOver = false this.isGameSuccess = false this.booms = [] for (var i=0;i<this.width;i++){ this.maps.push([]) this.mask.push([]) for (var j=0;j<this.height;j++){ this.maps[i].push(0), this.mask[i].push(0) } } var initBooms = [] while (initBooms.length < this.boomNum){ var xy = [ parseInt(Math.random()*this.width), parseInt(Math.random()*this.height) ] var hasSame = false; for (var b =0; b<initBooms.length;b++){ if(initBooms[b][0] == xy[0] && initBooms[b][1] == xy[1]){ hasSame = true; break; } } if (!hasSame){ initBooms.push(xy) this.maps[xy[0]][xy[1]] = -1; } } this.booms = initBooms; for (var i=0;i<this.width;i++){ for (var j=0;j<this.height;j++){ if(this.maps[i][j] !== -1){ var boomSum = 0; if(i > 0) { if (j > 0 && this.maps[i-1][j-1] == -1) boomSum ++; if (this.maps[i-1][j] == -1) boomSum ++; if (j < this.height - 1 && this.maps[i-1][j+1] == -1) boomSum ++; } if(i < this.width - 1) { if (j > 0 && this.maps[i+1][j-1] == -1) boomSum ++; if (this.maps[i+1][j] == -1) boomSum ++; if (j < this.height - 1 && this.maps[i+1][j+1] == -1) boomSum ++; } if (j > 0 && this.maps[i][j-1] == -1) boomSum ++; if (j < this.height - 1 && this.maps[i][j+1] == -1) boomSum ++; this.maps[i][j] = boomSum } } } this.$emit('init',{maps:this.maps}) }, setMask(i,j,action){ // action 可以是 open 或 mask this.lastAction = action; if (this.isGameOver || this.isGameSuccess){ return; } else if (action === 'open'){ if (this.maps[i][j] === -1){ for(var b=0;b<this.booms.length;b++){ var theBoomXY = this.booms[b]; if(this.mask[theBoomXY[0]][theBoomXY[1]] != -1){ this.mask[theBoomXY[0]][theBoomXY[1]] = 1 } } this.isGameOver = true; for (var i=0;i<this.width;i++){ for (var j=0;j<this.height;j++){ if(this.mask[i][j] == -1 && this.maps[i][j] != -1) this.mask[i][j] = 2; } } this.$forceUpdate() this.$emit('result', {code:-1, msg:'failed'}) } else{ this.canIOpen(i,j); } } else{ if(this.mask[i][j] == 0) this.mask[i][j] = -1; else if(this.mask[i][j] == -1) this.mask[i][j] = 0; // console.log(i,j,this.mask[i][j]) this.$nextTick(function(){ this.$forceUpdate() }) } }, canIOpen(i,j,level=0){ if (this.lastAction != 'open'){ // 防止误触 return; } if(this.maps[i][j] == -1){ if (level <= 1){ this.setMask(i,j,'open') } return; } this.mask[i][j] = 1; var boomSum = 0; if(i > 0) { if (j > 0 && this.mask[i-1][j-1] == -1) boomSum ++; if (this.mask[i-1][j] == -1) boomSum ++; if (j < this.height-1 && this.mask[i-1][j+1] == -1) boomSum ++; } if(i <this.width - 1) { if (j > 0 && this.mask[i+1][j-1] == -1) boomSum ++; if (this.mask[i+1][j] == -1) boomSum ++; if (j < this.height-1 && this.mask[i+1][j+1] == -1) boomSum ++; } if (j > 0 && this.mask[i][j-1] == -1) boomSum ++; if (j < this.height-1 && this.mask[i][j+1] == -1) boomSum ++; // console.log(boomSum) if (this.maps[i][j] <= boomSum && this.maps[i][j] != -1){ if(i > 0) { if (j > 0 && this.mask[i-1][j-1] == 0) this.canIOpen(i-1,j-1,level+1); if (this.mask[i-1][j] == 0) this.canIOpen(i-1,j,level+1); if (j < this.height-1 && this.mask[i-1][j+1] == 0) this.canIOpen(i-1,j+1,level+1); } if(i <this.width - 1) { if (j > 0 && this.mask[i+1][j-1] == 0) this.canIOpen(i+1,j-1,level+1); if (this.mask[i+1][j] == 0) this.canIOpen(i+1,j,level+1); if (j < this.height-1 && this.mask[i+1][j+1] == 0) this.canIOpen(i+1,j+1,level+1); } if (j > 0 && this.mask[i][j-1] == 0) this.canIOpen(i,j-1,level+1); if (j < this.height-1 && this.mask[i][j+1] == 0) this.canIOpen(i,j+1,level+1); } this.$set(this,'mask',this.mask) // console.log(this.mask) this.$forceUpdate(); }, } } </script> <style> .content { display: flex; flex-direction: column; align-items: center; justify-content: space-between; background-color: #1b5470; } .contentMap { width: 100%; overflow: hidden; } .titleLine{ width: 90%; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding-top: 10px; padding-bottom: 10px; } .titleLine image{ width: 60upx; height: 60upx; } .placeInRow{ display: flex; flex-direction: row; justify-content: center; } .block{ width: 60upx; height: 60upx; background-color: #ffffffc4; border: #9a9a9a solid 1px; display: flex; flex-direction: column; align-items: center; justify-content: center; } .mask{ background-color: #e6e6e6; box-shadow: 2px 2px 5px 5px #bcbcbc inset; border: #8d8d8d solid 1px; } image{ width: 45upx; height: 45upx; } </style>