數組方法css
數學方法html
整體流程以下所示vue
command (keyCode) { // 總部 this.WhetherToRotate(keyCode) // 是否須要將上下操做轉換爲左右操做 this.Init() // 數據初始化 合併數字 this.IfInvalid() // 判斷是否無效 this.Rendering(keyCode) // 渲染到頁面 }
首先先將基本的 HTML 標籤跟 CSS 樣式寫出來git
因爲用的 vue ,因此渲染 html 部分的代碼不用咱們去手寫github
<template> <div id='app'> <div class='total'>總分: {{this.total}} 分</div> // {{}} 這個中間表示 JavaScript 表達式 <div class='main'> <div class='row' v-for='(items,index) of arr' :key='index'> // v-for表示循環渲染當前元素,具體渲染次數爲 arr.length <div :class='`c-${item} item`' v-for='(item,index) of items' :key='index' >{{item>0?item:''}}</div> // :class= 表示將 JavaScript 變量做爲類名 </div> </div> <footer> <h2>玩法說明:</h2> <p>1.用鍵盤上下左右鍵控制數字走向</p> <p>2.當點擊了一個方向時,格子中的數字會所有往那個方向移動,直到不能再移動,若是有相同的數字則會合並</p> <p>3.當格子中再也不有可移動和可合併的數字時,遊戲結束</p> </footer> </div> </template>
css因爲太長就不放了跟以前基本沒有太多區別數組
接下來是數據的初始化app
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 添加事件監聽 dom
爲何在 mounted 添加事件?
咱們先了解下vue的生命週期函數
因此若是太早的話可能找不到 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 }
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的概率我改的比較大,因此相應的下降了一些難度,具體體如今當全部數字都在左邊(最邊上),且數字與數字間沒有空隙,再按左也會生成數字