成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[ 邏輯鍛煉] 用 JavaScript 做一個小游戲 ——2048 (詳解版)

gxyz / 2059人閱讀

前言

這次使用了 vue 來編寫 2048,主要目的是溫習(xí)一下 vue。

但是好像沒有用到太多 vue 的東西,==! 估計可能習(xí)慣了不用框架吧

之前由于時間關(guān)系沒有對實現(xiàn)過程詳細(xì)講解,本次會詳細(xì)講解下比較繞的函數(shù)

由于篇幅問題簡單的函數(shù)就不做詳解了

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

實現(xiàn)功能

數(shù)字合并

當(dāng)前總分計算

沒有可移動的數(shù)字時不進行任何操作

沒有可移動,可合并的數(shù)字,并且不能新建時游戲失敗

達到 2048 結(jié)束游戲

用到的知識

ES6

vue 部分模板語法

vue 生命周期

數(shù)組方法

reverse()

push()

unshift()

some()

forEach()

reduceRight()

數(shù)學(xué)方法

Math.abs()

Math.floor()

具體實現(xiàn)

是否需要將上下操作轉(zhuǎn)換為左右操作

數(shù)據(jù)初始化

合并數(shù)字

判斷操作是否無效

渲染到頁面

隨機創(chuàng)建數(shù)字

計算總分

判斷成功

判斷失敗

總體流程如下所示

command (keyCode) { // 總部
      this.WhetherToRotate(keyCode) // 是否需要將上下操作轉(zhuǎn)換為左右操作
      this.Init() // 數(shù)據(jù)初始化 合并數(shù)字
      this.IfInvalid() // 判斷是否無效
      this.Rendering(keyCode) // 渲染到頁面
    }
初始化

首先先將基本的 HTML 標(biāo)簽跟 CSS 樣式寫出來

由于用的 vue ,所以渲染 html 部分的代碼不用我們?nèi)ナ謱?/p>

css由于太長就不放了跟之前基本沒有太多區(qū)別

接下來是數(shù)據(jù)的初始化

data () {
    return {
      arr: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], // 與頁面綁定的數(shù)組
      Copyarr: [[], [], [], []], // 用來數(shù)據(jù)操作的數(shù)組
      initData: [], // 包含數(shù)字詳細(xì)坐標(biāo)的數(shù)組
      haveGrouping: false, // 有可以合并的數(shù)字
      itIsLeft: false, // 是否為向左合并,默認(rèn)不是向左合并
      endGap: true, // 判斷最邊上有沒有空隙 默認(rèn)有空隙
      middleGap: true, // 真 為某行中間有空隙
      haveZero: true, // 當(dāng)前頁面有沒有 0
      total: 0, // 總分?jǐn)?shù)
      itIs2048: false, // 是否成功
      max: 2048 // 最高分?jǐn)?shù)
    }
  }

做好初始化看起來應(yīng)該是這樣的效果

添加事件監(jiān)聽

在 mounted 添加事件監(jiān)聽

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

beforeCreate 實例創(chuàng)建之前 在這個階段我們寫的代碼還沒有被運行

created 實例創(chuàng)建之后 在這個階段我們寫的代碼已經(jīng)運行了但是還沒有將 HTML 渲染到頁面

mounted 掛載之后 在這個階段 html 渲染到頁面了,可以取到 dom 節(jié)點

beforeUpdate 數(shù)據(jù)更新前 在我們需要重新渲染 html 前調(diào)用 類似執(zhí)行 warp.innerHTML = html; 之前

updated 數(shù)據(jù)更新后 在重新渲染 HTML 后調(diào)用

destroyed 實例銷毀后調(diào)用 將我們寫的代碼丟棄掉后調(diào)用

errorCaptured 當(dāng)捕獲一個來自子孫組件的錯誤時被調(diào)用 2.5.0+ 新增

注:我說的我們寫的代碼只是一種代指,是為了方便理解,并不是真正的指我們寫的代碼

所以如果太早的話可能找不到 dom 節(jié)點,太晚的話,可能不能第一時間進行事件的響應(yīng)

  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
      }
    }
  }
將操作簡化為只有左右

這段代碼我是某天半夢半醒想到的,可能思維不好轉(zhuǎn)過來,可以看看代碼下面的圖

這樣一來就將向上的操作轉(zhuǎn)換成了向左的操作
向下的操作就轉(zhuǎn)換成了向右的操作
這樣折騰下可以少寫一半的數(shù)字合并代碼

    WhetherToRotate (keyCode) { // 是否需要將上下操作轉(zhuǎn)換為左右操作
      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
      }
      // 將當(dāng)前操作做一個標(biāo)識
      if (keyCode === 37 || keyCode === 38) { // 數(shù)據(jù)轉(zhuǎn)換后只有左右操作
        this.itIsLeft = true
      } else if (keyCode === 39 || keyCode === 40) {
        this.itIsLeft = false
      }
    }

轉(zhuǎn)換代碼

    ToRotate (arr) { // 將數(shù)據(jù)從 x 到 y  y 到 x 相互轉(zhuǎn)換
      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
    }

數(shù)據(jù)初始化

數(shù)組中的 0 在這個小作品中僅用作占位,視為垃圾數(shù)據(jù),所以開始前需要處理掉,在結(jié)束后再加上

兩種數(shù)據(jù)格式,一種是包含詳細(xì)信息的,用來做一些判斷; 一種是純數(shù)字的二維數(shù)組,之后用來從新渲染頁面

 Init () { // 數(shù)據(jù)初始化
      this.initData = this.DataDetails() // 非零數(shù)字詳情
      this.Copyarr = this.NumberMerger() // 數(shù)字合并
    }
判斷是否無效
 IfInvalid () { // 判斷是否無效
      // 判斷每行中間有沒有空隙
      this.MiddleGap() // 真 為某行中間有空隙
      this.EndPointGap() // 在沒有中間空隙的條件下去判斷最邊上有沒有空隙
    }

判斷兩個數(shù)字之間有沒有空隙

    MiddleGap () { // 檢查每行中間有沒有空隙
      // 當(dāng)所有的數(shù)都是挨著的,那么 x 下標(biāo)兩兩相減并除以組數(shù)得到的絕對數(shù)是 1 ,比他大說明中間有空隙
      // 先將 x 下標(biāo)兩兩相減 并添加到新的數(shù)組
      let subarr = [[], [], [], []] // 兩兩相減的數(shù)據(jù)
      let sumarr = [] // 處理后的最終數(shù)據(jù)
      this.initData.forEach((items, index) => {
        items.forEach((item, i) => {
          if (typeof items[i + 1] !== "undefined") {
            subarr[index].push(item.col - items[i + 1].col)
          }
        })
      })
      // 將每一行的結(jié)果相加得到總和 然后除以每一行結(jié)果的長度
      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) // 真 為 有中間空隙
    }

判斷數(shù)字有沒有到最邊上

   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)
     }
     // 取出每行的第一個數(shù)的 x 下標(biāo)
     // 判斷是不是最邊上
     // 有不是的 說明邊上 至少有一個空隙
     // 是的話說明邊上沒有空隙
   }

這樣就將基本的判斷是否有效,是否失敗的條件都得到了
至于是否有可合并數(shù)字已經(jīng)在數(shù)據(jù)初始化時就得到了

現(xiàn)在所有數(shù)據(jù)應(yīng)該是這樣的

渲染頁面
Rendering (keyCode) {
      this.AddZero() // 先將占位符加上
      // 因為之前的數(shù)據(jù)都處理好了 所以只需要將上下的數(shù)據(jù)轉(zhuǎn)換回去就好了
      if (keyCode === 38 || keyCode === 40) { // 38 是上 40 是下
        this.Copyarr = this.ToRotate(this.Copyarr)
      }
      if (this.haveGrouping || this.endGap || this.middleGap) { // 滿足任一條件就說明可以新建隨機數(shù)字
        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("游戲結(jié)束!")
          // 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)
      }
    }

隨機空白處創(chuàng)建數(shù)字

這里之前是用遞歸函數(shù)的形式去判斷,但是用遞歸函數(shù)的話會有很多問題,最大的問題就是可能會堆棧溢出,或者卡死(遞歸函數(shù)就是在函數(shù)的最后還會去調(diào)用自己,如果不給出 return 的條件,很容易堆棧溢出或卡死)
所以這次改成抽獎的模式,將所有的空位的坐標(biāo)取到,放入一個數(shù)組,然后取這個數(shù)組的隨機下標(biāo),這樣我們會得到一個空位的坐標(biāo),然后再對這個空位進行處理

    RandomlyCreate (Copyarr) { // 隨機空白處創(chuàng)建新數(shù)字
      // 判斷有沒有可以新建的地方
      let max = this.max
      let copyarr = Copyarr
      let zero = [] // 做一個抽獎的箱子
      let subscript = 0 // 做一個拿到的獎品號
      let number = 0 // 獎品號兌換的物品
      // 找到所有的 0 將下標(biāo)添加到新的數(shù)組
      copyarr.forEach((items, index) => {
        items.forEach((item, i) => {
          if (item === 0) {
            zero.push({ x: index, y: i })
          }
        })
      })
      // 取隨機數(shù) 然后在空白坐標(biāo)集合中找到它
      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 的主要代碼
最后,因為隨機出現(xiàn)4的幾率我改的比較大,所以相應(yīng)的降低了一些難度,具體體現(xiàn)在當(dāng)所有數(shù)字都在左邊(最邊上),且數(shù)字與數(shù)字間沒有空隙,再按左也會生成數(shù)字

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/101236.html

相關(guān)文章

  • [ 邏輯鍛煉] JavaScript 一個游戲 ——2048 (初級

    摘要:前言前段時間發(fā)現(xiàn)網(wǎng)上有很多收費或公開課都有教用做小游戲的,然后自己就也想動手做一個,做這個小游戲主要是為了鍛煉自己的邏輯能力,也算是對之前一些學(xué)習(xí)的總結(jié)吧注實現(xiàn)方法完全是自己邊玩邊想的,所有些亂還請見諒另外配色方案是在某個游戲截屏,然后用吸 前言 前段時間發(fā)現(xiàn)網(wǎng)上有很多收費或公開課都有教用 js 做 2048 小游戲的,然后自己就也想動手做一個,做這個小游戲主要是為了鍛煉自己的邏輯能力...

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

    摘要:往期回顧在上一期的前端特效里,我們已經(jīng)把果汁混合的效果里面的圓形菜單做好了,如果你錯過了上篇文章今天我們要討論的是杯子里面的液體生成問題先來回顧下咱們的果汁混合效果吧果汁混合效果,掃描下方二維碼就看到啦我們接著上期的內(nèi)容來繼續(xù)往下講吧,本期 往期回顧 在上一期的【前端特效】?里,我們已經(jīng)把果汁混合的效果里面的圓形菜單做好了,如果你錯過了上篇文章今天我們要討論的是杯子里面的液體生成問題 ...

    宋華 評論0 收藏0
  • 微信應(yīng)號(小程序)資源匯總(1010更新)

    摘要:微信應(yīng)用號小程序資源匯總。每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補充。 wechat-weapp-resource 微信應(yīng)用號(小程序)資源匯總。 每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補充。 showImg(https://segmentfault.com/img/remote/1460000...

    趙春朋 評論0 收藏0
  • 微信應(yīng)號(小程序)資源匯總(1010更新)

    摘要:微信應(yīng)用號小程序資源匯總。每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補充。 wechat-weapp-resource 微信應(yīng)用號(小程序)資源匯總。 每天不定期整理和收集微信小程序相關(guān)資源,方便查閱和學(xué)習(xí),歡迎大家提交新的資源,完善和補充。 showImg(https://segmentfault.com/img/remote/1460000...

    piapia 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<