微信小程序 | 做一个小程序端的扫雷游戏

简介: 微信小程序 | 做一个小程序端的扫雷游戏

需求背景

扫雷游戏作为小时候入手电脑的入门级别游戏,其中有很多的编程知识可以学习得到。本文整理了一个完整的基于vue的小程序端扫雷游戏。我们可以从实现扫雷规则的过程中锻炼到vue的各类语法操作以及前端的样式调整,以及最常用的各类排版布局!😄


一、效果预览

1edaa204624f4fcc920bba9ecdd51e92.gif


二、技术关键点

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>


相关文章
|
2月前
|
存储 JSON 小程序
微信小程序入门之新建并认识小程序结构
微信小程序入门之新建并认识小程序结构
60 1
ly~
|
3月前
|
存储 供应链 小程序
除了微信小程序,PHP 还可以用于开发哪些类型的小程序?
除了微信小程序,PHP 还可用于开发多种类型的小程序,包括支付宝小程序、百度智能小程序、抖音小程序、企业内部小程序及行业特定小程序。在电商、生活服务、资讯、工具、娱乐、营销等领域,PHP 能有效管理商品信息、订单处理、支付接口、内容抓取、复杂计算、游戏数据、活动规则等多种业务。同时,在企业内部,PHP 可提升工作效率,实现审批流程、文件共享、生产计划等功能;在医疗和教育等行业,PHP 能管理患者信息、在线问诊、课程资源、成绩查询等重要数据。
ly~
88 6
|
2月前
|
小程序 JavaScript API
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
这篇文章介绍了如何在uni-app和微信小程序中实现将图片保存到用户手机相册的功能。
1036 0
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
|
2月前
|
小程序 前端开发 数据可视化
微信商城小程序WeiMall
微信商城小程序WeiMall
38 0
|
3月前
|
小程序 前端开发 API
微信小程序 - 调用微信 API 回调函数内拿不到 this 问题(解决方案)
本文讨论了在微信小程序中调用API回调函数时无法获取到`this`上下文的问题,并提供了解决方案。在回调函数中,使用一个变量(如`that`)来保存当前的`this`引用,然后在回调内部使用这个变量来访问当前页面的数据和方法。
|
4月前
|
移动开发 小程序 前端开发
|
4月前
|
JSON 小程序 JavaScript
超详细微信小程序开发学习笔记,看完你也可以动手做微信小程序项目
这篇文章是一份全面的微信小程序开发学习笔记,涵盖了从小程序介绍、环境搭建、项目创建、开发者工具使用、文件结构、配置文件、模板语法、事件绑定、样式规范、组件使用、自定义组件开发到小程序生命周期管理等多个方面的详细教程和指南。
|
4月前
|
小程序 前端开发
微信小程序商城,微信小程序微店 【毕业设计参考项目】
文章推荐了一个微信小程序商城项目作为毕业设计参考,该项目在Github上获得18.2k星,提供了详细的使用教程和前端页面实现,适合学习微信小程序开发和作为毕业设计项目。
微信小程序商城,微信小程序微店 【毕业设计参考项目】
|
4月前
|
小程序 前端开发 JavaScript
微信小程序实现微信支付(代码和注释很详细)
微信小程序实现微信支付(代码和注释很详细)
|
4月前
|
小程序 开发者
第一个微信小程序的初始化过程、小程序微信开发平台的下载、如何注册一个微信小程序的账号
这篇文章介绍了微信小程序的初始化过程,包括如何注册微信小程序账号、下载微信小程序开发者平台,并指导了新建小程序的详细步骤。
第一个微信小程序的初始化过程、小程序微信开发平台的下载、如何注册一个微信小程序的账号