[ロジックトレーニング]JavaScriptで小さなゲームを作る--2048(詳細版)

9353 ワード

前言
  • 今回は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はこの小作では占位としてのみ使用され、ゴミデータとみなされるため、開始前に処理する必要があり、終了後に
  • を加える.
  • の2つのデータフォーマットで、1つは詳細情報を含むもので、いくつかの判断に使用されます.1つは純粋な数字の2次元配列であり、その後、新しいレンダリングページ
  • から使用される.
     Init () { //      
          this.initData = this.DataDetails() //       
          this.Copyarr = this.NumberMerger() //     
        }

    無効かどうかを判断する
     IfInvalid () { //       
          //            
          this.MiddleGap() //           
          this.EndPointGap() //                       
        }
  • は、2つの数字の間に隙間があるかどうかを判断する
  •     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の条件を与えなければスタックオーバーフローやカードダウンしやすい)の可能性があるので今回は抽選モードに変更し、すべての空席の座標を取って、1つの配列に入れて、それからこの配列のランダムな下付きを取って、このように私達は1つの空席の座標を得ることができて、それから更にこの空席に対して処理します
        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が现れる确率は私が比较的に大きいため、それに応じていくつかの难易度を下げて、具体的にはすべての数字が左(最辺の上)に现れて、しかも数字と数字の间に隙间がなくて、更に左を押しても数字を生成します