微信小程序|从零动手实现俄罗斯方块

简介: 微信小程序|从零动手实现俄罗斯方块

需求背景

俄罗斯方块作为童年时期的经典怀旧游戏,该游戏我们更多的是在接机或者是pc端进行的体验,这次我们自己动手实现一次在web端,以及在移动端的俄罗斯方块,想要什么效果那我们自己造。😁😂🤣😃😄😅😆


一、效果预览

8ada436c056a4b51b6fd50a18b8d8604.gif


二、技术关键点

2.1 技术栈

  • 项目使用了Vue 2.0的语法规则,并非原生的微信小程序语法!
  • 兼容了uniapp框架的扩展能力!可以借助uniapp技术将vue的语法构建打包成微信小程序语法。


2.2 技术难点

  • (1)方块的移动

俄罗斯方块中最重要的模块莫非在于这个宫格棋盘。在宫格棋盘中要实现模块的运动和变化,其中关键点在于控制某个模块形状在棋盘中的现实状态。

对于棋盘我们使用一个二维数组实现: block[][]

特别对其下落的模块进行形状的预先定义,然后再根据用户操作进行选取:

9e506e8772254395b3a3664278176012.png


  • (2)键盘的控制
    要控制方块的下落和形状,我们得采用键盘对方块进行控制。

这时我们需要采用监听键盘得事件:

  document.addEventListener('keydown', function(e) {
        // console.log("----->当前按键keyCode:" + e.keyCode);
        switch (e.keyCode) {
          case 37:
            that.moveLeft();
            break;
          case 38:
            that.rotateBlock();
            break;
          case 39:
            that.moveRight();
            break;
          case 40:
            that.moveDown();
            break;
        }
      })


三、完整源码

<template>
  <view class="content">
    <view class="title">
      <view>分数:{{score[0]}}</view>
      <view class="blockMinMap">
        <view v-for="(line, i) in blocks[nextBlock[0]][nextBlock[1]]" :key="'minimap-line-'+i" class="line">
          <view v-for="(b, j) in line" :key="'minimap-line-'+i+'-block-'+j" :class="{'block':b=1}"></view>
        </view>
      </view>
      <view>等级:{{parseInt(score[0]/score[1])}}</view>
    </view>
    <view class="gameView" :style="{height:gameViewHeight+'px'}">
      <view v-for="(line, i) in getTrueMap()" :key="'map-line-'+i" class="line">
        <view v-for="(b, j) in line" :key="'map-line-'+i+'-block-'+j" :class="{'block':b>0}"
         :style="{width:getGameViewBlockSize()+'px', height:getGameViewBlockSize()+'px'}"
        ></view>
      </view>
    </view>
  </view>
</template>
<script>
  export default {
    data() {
      return {
        randomIndex:1,
        //地图大小
        mapSize:[18, 10],
        //下降时间:开始,结束,每升一级减少等待时间
        downSpeed: [1500, 200, 100],
        //分数:现在分数,多少分升一级, step
        score: [0, 10, 1],
        //地图
        map: [],
        //7种方块,及其朝向
        blocks: [
          [[[0,1,1],[1,1,0]],[[1,0],[1,1],[0,1]]],
          [[[1,1,0],[0,1,1]],[[0,1],[1,1],[1,0]]],
          [[[1,1,1,1]],[[1],[1],[1],[1]]],
          [[[1,1],[1,1]]],
          [[[0,1,0],[1,1,1]],[[0,1],[1,1],[0,1]],[[1,1,1],[0,1,0]],[[1,0],[1,1],[1,0]]],
          [[[0,0,1],[1,1,1]],[[1,1],[0,1],[0,1]],[[1,1,1],[1,0,0]],[[1,0],[1,0],[1,1]]],
          [[[1,0,0],[1,1,1]],[[0,1],[0,1],[1,1]],[[1,1,1],[0,0,1]],[[1,1],[1,0],[1,0]]],
        ],
        //方块开始位置
        startPosition:[0,4],
        //方块现在位置
        blockPosition:[0,0],
        //当前方块限制:方块种类,方块朝向
        nowBlock:[0,0],
        //下一个方块限制: 方块种类,方块朝向
        nextBlock:[0,0],
        //游戏窗口高度:
        gameViewHeight:450,
        //游戏结束
        gameOver:false,
        //游戏循环体
        gameUpdateFunc:null,
      }
    },
    mounted() {
      this.refreshNextBlock()
      this.refreshNextBlock()
    },
    onLoad() {
      this.initGame()
      var that = this
      document.addEventListener('keydown', function(e) {
        // console.log("----->当前按键keyCode:" + e.keyCode);
        switch (e.keyCode) {
          case 37:
            that.moveLeft();
            break;
          case 38:
            that.rotateBlock();
            break;
          case 39:
            that.moveRight();
            break;
          case 40:
            that.moveDown();
            break;
        }
      })
    },
    methods: {
      initGame(){
        var that = this;
        this.initMap()
        this.score[0] = 0;
        this.refreshNextBlock()
        this.refreshNextBlock()
        this.gameOver = false;
        clearInterval(that.gameUpdateFunc)
        that.gameUpdateFunc = null;
        this.$nextTick(function(){
          that.tryGetGameHeight()
          that.gameUpdate()
        })
      },
      gameUpdate(){
        var that = this;
        that.randomIndex = Math.floor(Math.random()*5+1)
        that.gameOver = false;
        that.gameUpdateFunc = setInterval(()=>{
          //游戏循环体
          that.moveDown()
          //更新
          clearInterval(that.gameUpdateFunc)
          //游戏未结束时触发
          if(!that.gameOver){
            that.gameUpdate()
          }
          else{
            uni.showModal({
              showCancel:false,
              confirmText:'再来一局',
              title:'游戏结束',
              content:'最终分数:'+this.score[0],
              success:(res)=>{
                if(res.confirm){
                  that.initGame()
                }
              }
            })
          }
        }, Math.max(
          (this.downSpeed[0]-this.downSpeed[2]*parseInt(this.score[0]/this.score[1])), 
          this.downSpeed[1]
        ))
      },
      moveDown(){
        //获取底部砖块情况
        if(this.gameOver) return;
        var bottomPosition = []
        var theBlock = this.blocks[this.nowBlock[0]][this.nowBlock[1]]
        for (var i=0;i<theBlock[0].length;i++){
          bottomPosition.push(0)
        }
        for (var i=0;i<theBlock.length;i++){
          for (var j=0;j<theBlock[i].length;j++){
            if(theBlock[i][j] > 0){
              bottomPosition[j] = Math.max(bottomPosition[j], i)
            }
          }
        }
        // console.log(bottomPosition)
        var canMove = true;
        for (var i=0;i<bottomPosition.length;i++){
          if(this.blockPosition[0]+bottomPosition[i]+1 >= this.mapSize[0]){
            canMove = false;
            break;
          }
          else if(this.map[
              this.blockPosition[0]+bottomPosition[i]+1
            ][
              this.blockPosition[1]+i
            ] > 0){
            canMove = false;
            break;
          }
        }
        if(canMove){
          this.blockPosition[0]+=1;
          this.$forceUpdate()
        }
        else{
          //如果当前Y坐标依旧是0,游戏结束
          if(this.blockPosition[0]<=0){
            this.gameOver = true;
          }
          //触底不能移动, 锁死方块,将方块的值赋予map
          else{
            for (var i=0;i<theBlock.length;i++){
              for (var j=0;j < theBlock[i].length;j++){
                // console.log(this.blockPosition[0]+i, this.blockPosition[1]+j)
                // console.log(this.map.length, this.map[0].length)
                this.map[this.blockPosition[0]+i][this.blockPosition[1]+j] = 
                  Math.max(theBlock[i][j], 
                    this.map[this.blockPosition[0]+i][this.blockPosition[1]+j]);
              }
            }
            this.$forceUpdate()
            //判断当前是否有无可能消除行
            this.checkMapScore()
            //更新下个方块
            this.refreshNextBlock()
          }
        }
      },
      rotateBlock(){
        if(this.gameOver) return;
        var nextStyle = (this.nowBlock[1]+1) % this.blocks[this.nowBlock[0]].length;
        //判断当前的状态是否存在位置
        var canChange = true;
        var changeBlock = this.blocks[this.nowBlock[0]][nextStyle];
        if(this.blockPosition[1]+changeBlock[0].length > this.mapSize[1]){
          //x超出
          canChange = false;
        }
        else if(this.blockPosition[0]+changeBlock.length > this.mapSize[0]){
          //y超出
          canChange = false;
        }
        else{
          for (var i=0;i<changeBlock.length;i++){
            for (var j=0;j<changeBlock[i].length;j++){
              //旋转后部分和原始map重合
              if (changeBlock[i][j] > 0 && 
                this.map[this.blockPosition[0]+i][this.blockPosition[1]+j] > 0){
                  canChange = false;
                  break;
                }
            }
          }
        }
        if(canChange){
          this.nowBlock[1] = nextStyle
          this.$forceUpdate()
        }
      },
      moveLeft(){
        //判断是否可以左移
        if(this.gameOver) return;
        var theBlock = this.blocks[this.nowBlock[0]][this.nowBlock[1]];
        var leftPosition = [];
        for (var i=0;i<theBlock.length;i++){
          leftPosition.push(-1)
          for (var j=0;j<theBlock[i].length;j++){
            if(leftPosition[i] === -1 && theBlock[i][j] > 0){
              leftPosition[i] = j;
            }
          }
        }
        // console.log(leftPosition)
        var canMove = true;
        for (var i=0;i<leftPosition.length;i++){
          if (this.blockPosition[1]+leftPosition[i] == 0){
            canMove = false;
            break;
          }
          else if (this.map[
                this.blockPosition[0]+i
              ][
                this.blockPosition[1]+leftPosition[i]-1
              ] > 0){
            canMove = false;
            break;
          }
        }
        if(canMove){
          this.blockPosition[1] -= 1;
          this.$forceUpdate()
        }
      },
      moveRight(){
        //判断是否可以左移
        if(this.gameOver) return;
        var theBlock = this.blocks[this.nowBlock[0]][this.nowBlock[1]];
        var rightPosition = [];
        for (var i=0;i<theBlock.length;i++){
          rightPosition.push(0)
          for (var j=0;j<theBlock[i].length;j++){
            if(theBlock[i][j] > 0){
              rightPosition[i] = j;
            }
          }
        }
        // console.log(rightPosition)
        var canMove = true;
        for (var i=0;i<rightPosition.length;i++){
          if (this.blockPosition[1]+rightPosition[i]+1 >= this.mapSize[1]){
            canMove = false;
            break;
          }
          else if (this.map[
                this.blockPosition[0]+i
              ][
                this.blockPosition[1]+rightPosition[i]+1
              ] > 0){
            canMove = false;
            break;
          }
        }
        if(canMove){
          this.blockPosition[1] += 1;
          this.$forceUpdate()
        }
      },
      checkMapScore(){
        var newMap = []
        // 计算score并消去满足的行
        for (var i=0; i < this.map.length; i++){
          var lineSum = 0;
          for(var j=0; j < this.map[i].length;j++){
            lineSum += Math.min(this.map[i][j], 1);
          }
          // console.log(i, lineSum)
          if(lineSum >= this.map[i].length){
            this.score[0] += this.score[2];
          }
          else{
            newMap.push(JSON.parse(JSON.stringify(this.map[i])))
          }
        }
        //补充缺失的行
        while (newMap.length < this.map.length){
          var aline = []
          for (var i=0;i<this.map[0].length;i++) aline.push(0)
          newMap.unshift(aline)
        }
        this.map = newMap;
        this.$forceUpdate();
      },
      initMap(){
        this.map = []
        for (var i=0;i<this.mapSize[0];i++){
          this.map.push([])
          for (var j=0;j<this.mapSize[1];j++){
            this.map[i].push(0)
          }
        }
      },
      tryGetGameHeight(){
        var that = this;
        this.$nextTick(function(){
          that.gameViewHeight = uni.getSystemInfoSync().windowHeight - 170;
        })
      },
      getGameViewBlockSize(){
        var padding = 40
        return Math.min(
          parseInt((this.gameViewHeight-padding)/this.mapSize[0]),
          parseInt((uni.getSystemInfoSync().windowWidth-padding)/this.mapSize[1])
        )
      },
      refreshNextBlock(){
        this.nowBlock = this.nextBlock
        var nextBlock = [
          parseInt(Math.random()*this.blocks.length),
          0
        ]
        nextBlock[1] = parseInt(this.blocks[nextBlock[0]].length*Math.random())
        this.nextBlock = nextBlock
        this.blockPosition = JSON.parse(JSON.stringify(this.startPosition));
        // console.log(this.blockPosition)
        this.$forceUpdate()
      },
      getTrueMap(){
        //实际map是原始map+当前方块位置
        if(this.map.length != this.mapSize[0] || this.mapSize[1] != this.map[0].length){
          return this.map;
        }
        var trueMap = JSON.parse(JSON.stringify(this.map))
        var theBlock = this.blocks[this.nowBlock[0]][this.nowBlock[1]]
        // console.log(theBlock)
        for(var i=0; i<theBlock.length; i++){
          for(var j=0; j<theBlock[i].length; j++){
            // console.log(i+this.blockPosition[0], j+this.blockPosition[1])
            trueMap[i+this.blockPosition[0]][j+this.blockPosition[1]] = Math.max(
              theBlock[i][j],
              trueMap[i+this.blockPosition[0]][j+this.blockPosition[1]]
            );
          }
        }
        // console.log(trueMap)
        return trueMap;
      }
    }
  }
</script>
<style>
  .content {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-between;
    width: 100%;
        background-color: #020000d4;
          height: 100vh;
  }
  .title{
    display: flex;
    flex-direction: row;
    justify-content: space-around;
    align-items: center;
    width: 90%;
    padding: 8% 5px;
    height:58px;
    border: #d9d9d9 solid 5px;
        color: #fff;
        border-radius: 10px;
        font-weight: bold;
      margin-top: 10px;
      background: #3da5c482;
  }
  .blockMinMap{
    display: flex;
    flex-direction: column;
    width: 48px;
    height: 48px;
    align-items: center;
    justify-content: center;
  }
  .blockMinMap .line, .gameView .line{
    display: flex;
    flex-direction: row;
    align-items: center;
  }
  .blockMinMap .line view{
    width: 12px;
    height: 12px;
  }
  .gameView .line view{
    border: 1px solid #e6e6e6;
    width: 40upx;
    height: 40upx;
  }
  .blockMinMap .line .block{
    border: 1px solid #333333;
    width: 10px;
    height: 10px;
  }
  .gameView .line .block{
    border: 1px solid #333333;
    background-color: #4CD964;
    /* width: 40upx;
    height: 40upx; */
  }
  .block1{
    border: 1px solid #333333;
    background-color: #4CD964;
    /* width: 40upx;
    height: 40upx; */
  }
  .block2{
    border: 1px solid #333333;
    background-color: #0055ff;
  }
   .block3{
    border: 1px solid #333333;
    background-color: #ff007f;
  }
  .block4{
    border: 1px solid #333333;
    background-color: #ffaa00;
  }
  .block5{
    border: 1px solid #333333;
    background-color: #ff55ff;
  }
  .gameView{
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
  .buttonView{
    width: 90%;
    padding: 5% 5px;
    border-top: #C0C0C0 solid 1px;
    background-color: #FFFFFF;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
  }
  .buttonView view{
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    width: 30%;
    border: #555555 solid 1px;
    border-radius: 10px;
    height: 30px;
    background-color: #FFFFFF;
  }
  .buttonView view:active{
    background-color: #C0C0C0;
  }
</style>


相关文章
|
小程序 开发者
微信小程序实现俄罗斯方块
微信小程序实现俄罗斯方块
341 0
|
2月前
|
JSON 小程序 JavaScript
uni-app开发微信小程序的报错[渲染层错误]排查及解决
uni-app开发微信小程序的报错[渲染层错误]排查及解决
714 7
|
2月前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
761 1
|
2月前
|
小程序 前端开发 测试技术
微信小程序的开发完整流程是什么?
微信小程序的开发完整流程是什么?
153 7
ly~
|
3月前
|
存储 供应链 小程序
除了微信小程序,PHP 还可以用于开发哪些类型的小程序?
除了微信小程序,PHP 还可用于开发多种类型的小程序,包括支付宝小程序、百度智能小程序、抖音小程序、企业内部小程序及行业特定小程序。在电商、生活服务、资讯、工具、娱乐、营销等领域,PHP 能有效管理商品信息、订单处理、支付接口、内容抓取、复杂计算、游戏数据、活动规则等多种业务。同时,在企业内部,PHP 可提升工作效率,实现审批流程、文件共享、生产计划等功能;在医疗和教育等行业,PHP 能管理患者信息、在线问诊、课程资源、成绩查询等重要数据。
ly~
89 6
|
2月前
|
缓存 小程序 索引
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
uni-app开发微信小程序时vant组件van-tabs的使用陷阱及解决方案
257 1
|
2月前
|
小程序 前端开发 数据安全/隐私保护
微信小程序全栈开发中的身份认证与授权机制
【10月更文挑战第3天】随着移动互联网的发展,微信小程序凭借便捷的用户体验和强大的社交传播能力,成为企业拓展业务的新渠道。本文探讨了小程序全栈开发中的身份认证与授权机制,包括手机号码验证、微信登录、第三方登录及角色权限控制等方法,并强调了安全性、用户体验和合规性的重要性,帮助开发者更好地理解和应用这一关键技术。
92 5
|
2月前
|
小程序 前端开发 JavaScript
微信小程序全栈开发中的PWA技术应用
【10月更文挑战第3天】微信小程序作为新兴应用形态,凭借便捷体验与社交传播能力,成为企业拓展业务的新渠道。本文探讨了微信小程序全栈开发中的PWA技术应用,包括离线访问、后台运行、桌面图标及原生体验等方面,助力开发者提升小程序性能与用户体验。PWA技术在不同平台的兼容性、性能优化及用户体验是实践中需注意的关键点。
74 5
|
2月前
|
小程序 JavaScript API
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
这篇文章介绍了如何在uni-app和微信小程序中实现将图片保存到用户手机相册的功能。
1085 0
微信小程序开发之:保存图片到手机,使用uni-app 开发小程序;还有微信原生保存图片到手机
|
2月前
|
存储 小程序 安全
微信的开发管理都需要配置什么?
【10月更文挑战第17天】微信的开发管理都需要配置什么?
39 0