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

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

需求背景

扫雷游戏作为小时候入手电脑的入门级别游戏,其中有很多的编程知识可以学习得到。本文整理了一个完整的基于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>


相关文章
|
3月前
|
人工智能 小程序 前端开发
一个小程序轻量AR体感游戏,开发实现解决方案
针对青少年运动兴趣不足问题,AR体感游戏凭借沉浸式互动体验脱颖而出。结合小程序“AI运动识别”插件与WebGL渲染技术,可实现无需外设的轻量化AR健身游戏,如跳糕、切水果等,兼具趣味性与锻炼效果,适用于儿童健身及职工团建,即开即玩,低门槛高参与。
|
4月前
|
缓存 小程序 前端开发
商城/点餐/家政类小程序源码合集_微信抖音小程序源码开发从入门到精通实战
本文系统讲解如何利用现有源码快速开发商城、点餐、家政类微信/抖音小程序,涵盖环境搭建、核心功能实现、多平台部署与优化,提供完整技术方案。实战导向,助力开发者高效入门与落地。
|
4月前
|
小程序 PHP 图形学
热门小游戏源码(Python+PHP)下载-微信小程序游戏源码Unity发实战指南​
本文详解如何结合Python、PHP与Unity开发并部署小游戏至微信小程序。涵盖技术选型、Pygame实战、PHP后端对接、Unity转换适配及性能优化,提供从原型到发布的完整指南,助力开发者快速上手并发布游戏。
|
4月前
|
存储 小程序 Java
热门小程序源码合集:微信抖音小程序源码支持PHP/Java/uni-app完整项目实践指南
小程序已成为企业获客与开发者创业的重要载体。本文详解PHP、Java、uni-app三大技术栈在电商、工具、服务类小程序中的源码应用,提供从开发到部署的全流程指南,并分享选型避坑与商业化落地策略,助力开发者高效构建稳定可扩展项目。
|
8月前
|
小程序 前端开发 Android开发
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
1712 29
小程序微信分享功能如何开发?开放平台已绑定仍不能使用的问题?-优雅草卓伊凡
|
8月前
|
JSON 监控 小程序
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
915 14
微信百度字节小程序包过大解决方案(实战经验总结)-优雅草卓伊凡|果果|小无
|
存储 JSON 小程序
微信小程序入门之新建并认识小程序结构
微信小程序入门之新建并认识小程序结构
249 1
|
10月前
|
存储 移动开发 小程序
校园圈子系统小程序(圈子论坛、私信聊天、资料共享、二手交易、兼职,跑腿)开源码开发/微信公众号、小程序、H5多端账号同步/搭建多城市的综合社交生活平台
基于开源技术栈构建的校园圈子系统小程序,整合社交与生活服务功能,涵盖兴趣圈子、私信聊天、资料共享、二手交易、兼职跑腿等六大核心模块。通过多端账号同步(微信公众号/小程序/H5),实现数据实时交互,满足学生群体的多元化需求。项目精准锚定校园市场,以“社交+服务”双轮驱动,提供一站式解决方案,支持快速部署与多校区运营,同时具备广告、佣金、会员等多元变现能力,是打造校园生态的理想工具。
1130 2
校园圈子系统小程序(圈子论坛、私信聊天、资料共享、二手交易、兼职,跑腿)开源码开发/微信公众号、小程序、H5多端账号同步/搭建多城市的综合社交生活平台
|
11月前
|
小程序 数据安全/隐私保护 开发者
【02】微信支付商户申请下户到配置完整流程-微信开放平台申请APP应用-微信商户支付绑定appid-公众号和小程序分别申请appid-申请+配置完整流程-优雅草卓伊凡
【02】微信支付商户申请下户到配置完整流程-微信开放平台申请APP应用-微信商户支付绑定appid-公众号和小程序分别申请appid-申请+配置完整流程-优雅草卓伊凡
832 3
|
11月前
|
人工智能 小程序 程序员
【视频测评 DEMO 参考】VSCode 神级 AI 插件通义灵码:完全免费+实战教程+微信贪吃蛇小程序
VSCode 神级 AI 插件通义灵码:完全免费+实战教程+微信贪吃蛇小程序
859 8

热门文章

最新文章