资讯专栏INFORMATION COLUMN

[ 逻辑锻炼] 用 JavaScript 做一个小游戏 ——2048 (详解版)

gxyz / 2050人阅读

前言

这次使用了 vue 来编写 2048,主要目的是温习一下 vue。

但是好像没有用到太多 vue 的东西,==! 估计可能习惯了不用框架吧

之前由于时间关系没有对实现过程详细讲解,本次会详细讲解下比较绕的函数

由于篇幅问题简单的函数就不做详解了

代码地址: https://github.com/yhtx1997/S...

实现功能

数字合并

当前总分计算

没有可移动的数字时不进行任何操作

没有可移动,可合并的数字,并且不能新建时游戏失败

达到 2048 结束游戏

用到的知识

ES6

vue 部分模板语法

vue 生命周期

数组方法

reverse()

push()

unshift()

some()

forEach()

reduceRight()

数学方法

Math.abs()

Math.floor()

具体实现

是否需要将上下操作转换为左右操作

数据初始化

合并数字

判断操作是否无效

渲染到页面

随机创建数字

计算总分

判断成功

判断失败

总体流程如下所示

command (keyCode) { // 总部
      this.WhetherToRotate(keyCode) // 是否需要将上下操作转换为左右操作
      this.Init() // 数据初始化 合并数字
      this.IfInvalid() // 判断是否无效
      this.Rendering(keyCode) // 渲染到页面
    }
初始化

首先先将基本的 HTML 标签跟 CSS 样式写出来

由于用的 vue ,所以渲染 html 部分的代码不用我们去手写

css由于太长就不放了跟之前基本没有太多区别

接下来是数据的初始化

data () {
    return {
      arr: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], // 与页面绑定的数组
      Copyarr: [[], [], [], []], // 用来数据操作的数组
      initData: [], // 包含数字详细坐标的数组
      haveGrouping: false, // 有可以合并的数字
      itIsLeft: false, // 是否为向左合并,默认不是向左合并
      endGap: true, // 判断最边上有没有空隙 默认有空隙
      middleGap: true, // 真 为某行中间有空隙
      haveZero: true, // 当前页面有没有 0
      total: 0, // 总分数
      itIs2048: false, // 是否成功
      max: 2048 // 最高分数
    }
  }

做好初始化看起来应该是这样的效果

添加事件监听

在 mounted 添加事件监听

为什么在 mounted 添加事件?
我们先了解下vue的生命周期

beforeCreate 实例创建之前 在这个阶段我们写的代码还没有被运行

created 实例创建之后 在这个阶段我们写的代码已经运行了但是还没有将 HTML 渲染到页面

mounted 挂载之后 在这个阶段 html 渲染到页面了,可以取到 dom 节点

beforeUpdate 数据更新前 在我们需要重新渲染 html 前调用 类似执行 warp.innerHTML = html; 之前

updated 数据更新后 在重新渲染 HTML 后调用

destroyed 实例销毁后调用 将我们写的代码丢弃掉后调用

errorCaptured 当捕获一个来自子孙组件的错误时被调用 2.5.0+ 新增

注:我说的我们写的代码只是一种代指,是为了方便理解,并不是真正的指我们写的代码

所以如果太早的话可能找不到 dom 节点,太晚的话,可能不能第一时间进行事件的响应

  mounted () {
    window.onkeydown = e => {
      switch (e.keyCode) {
        case 37:
          //  ←
          console.log("←")
          this.Command(e.keyCode)
          break
        case 38:
          //  ↑
          console.log("↑")
          this.Command(e.keyCode)
          break
        case 39:
          //  →
          this.Command(e.keyCode)
          console.log("→")
          break
        case 40:
          //  ↓
          console.log("↓")
          this.Command(e.keyCode)
          break
      }
    }
  }
将操作简化为只有左右

这段代码我是某天半梦半醒想到的,可能思维不好转过来,可以看看代码下面的图

这样一来就将向上的操作转换成了向左的操作
向下的操作就转换成了向右的操作
这样折腾下可以少写一半的数字合并代码

    WhetherToRotate (keyCode) { // 是否需要将上下操作转换为左右操作
      if (keyCode === 38 || keyCode === 40) { // 38 是上 40 是下
        this.Copyarr = this.ToRotate(this.arr)
      } else if (keyCode === 37 || keyCode === 39) { // 37 是左 39 是右
        [...this.Copyarr] = this.arr
      }
      // 将当前操作做一个标识
      if (keyCode === 37 || keyCode === 38) { // 数据转换后只有左右操作
        this.itIsLeft = true
      } else if (keyCode === 39 || keyCode === 40) {
        this.itIsLeft = false
      }
    }

转换代码

    ToRotate (arr) { // 将数据从 x 到 y  y 到 x 相互转换
      let afterCopyingArr = [[], [], [], []]
      for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr[i].length; j++) {
          afterCopyingArr[i][j] = arr[j][i]
        }
      }
      return afterCopyingArr
    }

数据初始化

数组中的 0 在这个小作品中仅用作占位,视为垃圾数据,所以开始前需要处理掉,在结束后再加上

两种数据格式,一种是包含详细信息的,用来做一些判断; 一种是纯数字的二维数组,之后用来从新渲染页面

 Init () { // 数据初始化
      this.initData = this.DataDetails() // 非零数字详情
      this.Copyarr = this.NumberMerger() // 数字合并
    }
判断是否无效
 IfInvalid () { // 判断是否无效
      // 判断每行中间有没有空隙
      this.MiddleGap() // 真 为某行中间有空隙
      this.EndPointGap() // 在没有中间空隙的条件下去判断最边上有没有空隙
    }

判断两个数字之间有没有空隙

    MiddleGap () { // 检查每行中间有没有空隙
      // 当所有的数都是挨着的,那么 x 下标两两相减并除以组数得到的绝对数是 1 ,比他大说明中间有空隙
      // 先将 x 下标两两相减 并添加到新的数组
      let subarr = [[], [], [], []] // 两两相减的数据
      let sumarr = [] // 处理后的最终数据
      this.initData.forEach((items, index) => {
        items.forEach((item, i) => {
          if (typeof items[i + 1] !== "undefined") {
            subarr[index].push(item.col - items[i + 1].col)
          }
        })
      })
      // 将每一行的结果相加得到总和 然后除以每一行结果的长度
      subarr.forEach((items) => {
        sumarr.push(items.reduceRight((a, b) => a + b, 0))
      })
      sumarr = sumarr.map((item, index) => Math.abs(item / subarr[index].length))
      // 最后判断有没有比 1 大的值
      sumarr.some(item => item > 1)
      this.middleGap = sumarr.some(item => item > 1) // 真 为 有中间空隙
    }

判断数字有没有到最边上

   EndPointGap () { // 检查最边上有没有空隙
     // 判断是向左还是向右 因为左右的判断是不一样的
     this.endGap = true
     let end
     let initData = this.initData
     if (this.itIsLeft) {
       end = 0
       this.endGap = initData.some(items => items.length !== 0 ? items[0].col !== end : false)
     } else {
       end = 3
       this.endGap = initData.some(items => items.length !== 0 ? items[items.length - 1].col !== end : false)
     }
     // 取出每行的第一个数的 x 下标
     // 判断是不是最边上
     // 有不是的 说明边上 至少有一个空隙
     // 是的话说明边上没有空隙
   }

这样就将基本的判断是否有效,是否失败的条件都得到了
至于是否有可合并数字已经在数据初始化时就得到了

现在所有数据应该是这样的

渲染页面
Rendering (keyCode) {
      this.AddZero() // 先将占位符加上
      // 因为之前的数据都处理好了 所以只需要将上下的数据转换回去就好了
      if (keyCode === 38 || keyCode === 40) { // 38 是上 40 是下
        this.Copyarr = this.ToRotate(this.Copyarr)
      }
      if (this.haveGrouping || this.endGap || this.middleGap) { // 满足任一条件就说明可以新建随机数字
        this.RandomlyCreate(this.Copyarr)
      } else if (this.haveZero) {
        // 都不满足 但是有空位不做失败判断
      } else {
      // 以上都不满足视为没有空位,不可合并
        if (this.itIs2048) { // 判断是否达成2048
          this.RandomlyCreate(this.Copyarr)
          alert("恭喜达成2048!")
          // 下面注释掉的可让游戏在点击弹框按钮后重新开始新游戏
          // this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
          // this.RandomlyCreate(this.arr)
        } else { //以上都不满足视为失败
          this.RandomlyCreate(this.Copyarr)
          alert("游戏结束!")
          // this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
          // this.RandomlyCreate(this.arr)
        }
      }
      if (this.itIs2048) { // 每次页面渲染完,都判断是否达成2048
        this.RandomlyCreate(this.Copyarr)
        alert("恭喜达成2048!")
        // this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
        // this.RandomlyCreate(this.arr)
      }
    }

随机空白处创建数字

这里之前是用递归函数的形式去判断,但是用递归函数的话会有很多问题,最大的问题就是可能会堆栈溢出,或者卡死(递归函数就是在函数的最后还会去调用自己,如果不给出 return 的条件,很容易堆栈溢出或卡死)
所以这次改成抽奖的模式,将所有的空位的坐标取到,放入一个数组,然后取这个数组的随机下标,这样我们会得到一个空位的坐标,然后再对这个空位进行处理

    RandomlyCreate (Copyarr) { // 随机空白处创建新数字
      // 判断有没有可以新建的地方
      let max = this.max
      let copyarr = Copyarr
      let zero = [] // 做一个抽奖的箱子
      let subscript = 0 // 做一个拿到的奖品号
      let number = 0 // 奖品号兑换的物品
      // 找到所有的 0 将下标添加到新的数组
      copyarr.forEach((items, index) => {
        items.forEach((item, i) => {
          if (item === 0) {
            zero.push({ x: index, y: i })
          }
        })
      })
      // 取随机数 然后在空白坐标集合中找到它
      subscript = Math.floor(Math.random() * zero.length)
      if (Math.floor(Math.random() * 10) % 3 === 0) {
        number = 4 // 三分之一的机会
      } else {
        number = 2 // 三分之二的机会
      }
      if (zero.length) {
        Copyarr[zero[subscript].x][zero[subscript].y] = number
        this.arr = Copyarr
      }
      this.total = 0
      this.arr.forEach(items => {
        items.forEach(item => {
          if (item === max && !this.itIs2048) {
            this.itIs2048 = true
          }
          this.total += item
        })
      })
    }

以上就是本次 2048 的主要代码
最后,因为随机出现4的几率我改的比较大,所以相应的降低了一些难度,具体体现在当所有数字都在左边(最边上),且数字与数字间没有空隙,再按左也会生成数字

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/101236.html

相关文章

  • [ 逻辑锻炼] JavaScript 一个游戏 ——2048 (初级

    摘要:前言前段时间发现网上有很多收费或公开课都有教用做小游戏的,然后自己就也想动手做一个,做这个小游戏主要是为了锻炼自己的逻辑能力,也算是对之前一些学习的总结吧注实现方法完全是自己边玩边想的,所有些乱还请见谅另外配色方案是在某个游戏截屏,然后用吸 前言 前段时间发现网上有很多收费或公开课都有教用 js 做 2048 小游戏的,然后自己就也想动手做一个,做这个小游戏主要是为了锻炼自己的逻辑能力...

    lidashuang 评论0 收藏0
  • 前端特效【第04期】|果汁混合效果-下

    摘要:往期回顾在上一期的前端特效里,我们已经把果汁混合的效果里面的圆形菜单做好了,如果你错过了上篇文章今天我们要讨论的是杯子里面的液体生成问题先来回顾下咱们的果汁混合效果吧果汁混合效果,扫描下方二维码就看到啦我们接着上期的内容来继续往下讲吧,本期 往期回顾 在上一期的【前端特效】☜里,我们已经把果汁混合的效果里面的圆形菜单做好了,如果你错过了上篇文章今天我们要讨论的是杯子里面的液体生成问题 ...

    宋华 评论0 收藏0
  • 微信应号(小程序)资源汇总(1010更新)

    摘要:微信应用号小程序资源汇总。每天不定期整理和收集微信小程序相关资源,方便查阅和学习,欢迎大家提交新的资源,完善和补充。 wechat-weapp-resource 微信应用号(小程序)资源汇总。 每天不定期整理和收集微信小程序相关资源,方便查阅和学习,欢迎大家提交新的资源,完善和补充。 showImg(https://segmentfault.com/img/remote/1460000...

    赵春朋 评论0 收藏0
  • 微信应号(小程序)资源汇总(1010更新)

    摘要:微信应用号小程序资源汇总。每天不定期整理和收集微信小程序相关资源,方便查阅和学习,欢迎大家提交新的资源,完善和补充。 wechat-weapp-resource 微信应用号(小程序)资源汇总。 每天不定期整理和收集微信小程序相关资源,方便查阅和学习,欢迎大家提交新的资源,完善和补充。 showImg(https://segmentfault.com/img/remote/1460000...

    piapia 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<