前端純原生代碼完成2048

媒介

為何閑着沒事要做一個2048呢?還不是360前端星設計(2018春招實習生)要我做的。然後就花了幾天時刻做了一個2048小遊戲,兼容到pc端和部份挪動端(裝備有限,有的挪動瀏覽器真的沒兼容到或許是真的不想做兼容了)。僅供人人看看就好哈。

github地點: https://github.com/GDUTxxZ/20…

在線預覽:http://47.94.199.75/index.html (這個網址臨時有用。。今後點進去不知道又是我的什麼試驗作品。)

遊戲引見

我做完給朋儕看今後發明不是每個人都玩過這個遊戲。簡樸引見一下遊戲內容好了。

《前端純原生代碼完成2048》

得勝前提: 拼湊出一個2048方塊
失利前提: 當前沒有可用方塊,而且一切方塊都不能夠和鄰近方塊兼并

代碼構造

index.html:

<div id="bg"></div><!-- 背景 -->
<div id="main"></div><!-- 實體 -->
<div id="alert">
    <span>遊戲失利</span>
    <button>再來一局</button>
<div>

#bg為背景圖,也就是空的灰色方塊,由於方塊挪動的時刻不能顯露底下的空缺
#main為實體,也就是遊戲中我們瞥見的包括数字的方塊
#alert為提示框,一最先display:none,當遊戲成功或許完畢的時刻,display:block
#alert span 失利音訊或許成功音訊

css的話,主假如關於動畫元素的設置:

base.css

#main .item {
    transition: all .3s ease-out;
    -moz-transition: all .3s ease-out; /* Firefox 4 */
    -webkit-transition: all .3s ease-out; /* Safari 和 Chrome */
    -o-transition: all .3s ease-out; /* Opera */
    left: 0px;
    top: 0px;
}

js的話主假如兩塊,util.js擔任了一些外圍函數(主要的是關於挪動端滑動事宜的封裝)的處置懲罰,2048.js就是頁面團體邏輯

util.js // 關於挪動端滑動事宜的封裝

const touchManager = (function () {
    let start = []
    let end = []
    let timeStamp = 0

    let manager = {}
    manager.touchstart = function (event) { // 紀錄下最先位置
        event.stopPropagation()

        timeStamp = event.timeStamp // 獵取點擊時的時刻
        let target = event.targetTouches[0]

        start = [target.pageX, target.pageY]
        end = [target.pageX, target.pageY]
        console.log('start')
    }
    manager.touchmove = function (event) { // 紀錄下挪動位置
        event.stopPropagation()
        event.preventDefault()

        let target = event.targetTouches[0]

        end = [target.pageX, target.pageY]
        console.log('move')
    }
    manager.touchend = function (event) { // 處置懲罰最先位置和挪動位置給出滑動方向
        event.stopPropagation()
        event.preventDefault()

        const abs = Math.abs

        let time = event.timeStamp - timeStamp // 獵取滑動操縱運用的時刻
        let moveX = end[0] - start[0]
        let moveY = end[1] - start[1]

        if (time > 500 || (abs(moveX) < 50 && abs(moveY) < 50)) { // 挪動間隔不夠或時刻太長就不認為是滑動
            return false
        } else {
            if (abs(moveX) >= abs(moveY)) { // 橫向挪動間隔較長
                console.log(moveX)
                return moveX > 0 ? 'right' : 'left'
            } else { // 縱向挪動間隔較長
                console.log(moveY)
                return moveY > 0 ? 'down' : 'up' 
            }
        }
    }
    return manager
})()

2048.js 主要由以下幾個數據構造和函數組成:

數據構造:

let data = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]] // 初始化
let emptyList = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] // 當前沒有元素的格子
const container = document.querySelector('#main') // 操縱實體
const bg = document.querySelector('#bg') // 背景板
const alert = document.querySelector('#alert') // 提示框
const alertText = alert.querySelector('span') // 提示框里的筆墨容器
const baseSize = parseInt(getDomStyle(container).width) // 基本畫板的size
let gameOver = false // 標誌遊戲是不是完畢
const animateTime = 300 // 單位ms, 動畫時長

這個處所保留了大批dom元素的援用,為了今後操縱的時刻削減獵取dom的機能斲喪

別的另有以下幾個函數:

main // 主程序
resize(el) // 調解元素寬高一致
createElement() // 在emptyList里找1-2個下標出來,給data增加新的元素,取值為{2, 4}
paint(el, data) // 用data在el里畫出每個格子
animate(move, arrow) // 傳入一個挪動行列,和挪動方向‘left’代表橫向,‘top’代表縱向
isWin(data) isLost(data, emptyList) // 推斷遊戲輸贏
win() lost() // 顯現輸贏音訊
replay() // 再來一局
moveHandle = {...} // 封裝了盤算挪動效果的函數

然後再從主程序看函數的流程:

function main () { // 主程序
    // 調解背景和實體寬高
    resize(container)
    resize(bg)
    
    // 初始化背景和實體元素
    paint(bg, data)
    // 建立1-2個初始元素
    createNewElement()
    paint(container, data)

    // 綁定事宜監聽器
    addEvent(window, 'keydown', function (event) { // 按鍵監聽
        if (gameOver) {
            return
        }
        let arrow = keyCodeMap[event.keyCode]
        switch (arrow) {
            case 'left':
            case 'up':
            case 'right':
            case 'down': {
                moveHandle.move(arrow)
                break
            }
        }
    })

    addEvent(alert.querySelector('button'), 'click', replay) // 再玩一次

    addEvent(container, 'touchstart', touchManager.touchstart)
    addEvent(container, 'touchmove', touchManager.touchmove)
    addEvent(container, 'touchend', function (event) {
        let arrow = touchManager.touchend(event)
        if (arrow) {
            moveHandle.move(arrow)
        }
    })
}

也等於:1.初始化 2. 綁定事宜監聽

然後就是怎樣盤算出挪動效果,以下用一個左滑盤算(moveHandle.moveleft)為例子

moveleft: function () { // 向左挪動
    // 盤算挪動后的data
    // 要挪動的元素的挪動坐標
    // 沒有元素的格子
    let newData = copy(data) // 獵取當前數據的一個copy
    let move = [] // 方塊挪動行列
    emptyList = []
    for (let i = 0; i < 4; i++) { // 一行行處置懲罰
        let newList = [] // 新行
        let oldList = data[i]

        for (let j = 0; j < 4; j++) { // 找到一切非0單位
            let value = newData[i][j]
            if (value !== 0) {
                newList.push(value)
            }
        }

        if (newList.length > 1) { // 兼并同類項
            for (let j = 0, len = newList.length; j < len - 1; j++) {
                if (newList[j] === newList[j + 1]) {
                    newList[j] *= 2
                    newList[j + 1] = 0
                    j++
                }
            }
            newList = newList.filter(item => item !== 0) // 過濾掉上一步發生的0
        }

        for (let j = newList.length; j < 4; j++) { // 補全數列尾部的0
            emptyList.push(i * 4 + j)
            newList.push(0)
        }

        newData[i] = newList

        // 發生每位元素挪動的坐標
        for (let j = 0, k = 0,tag = false; j < 4; j++) { // j為舊元素位置,k為挪動到的位置
            if (newList[k] === 0) { // 假如沒有要挪動的位置了
                break
            } else if (oldList[j] === newList[k]) { // j挪動到k位置
                if (j !== k) {
                    move.push({
                        start: [i, j],
                        end: [i, k]
                    })
                }
                k++
            } else if (oldList[j] === newList[k] / 2) { // 兩個元素合成k位置的元素
                move.push({
                    start: [i, j],
                    end: [i, k]
                })
                if (tag) {
                    k++
                }
                tag = !tag
            }
        }
    }

    return {
        newData: newData,
        move: move
    }
}

這個函數末了產出的是 newData 盤算后的 data, move 方塊的挪動行列,形如[{start: [x1, y1], end: [x2, y2]}, … ]

然後怎樣應用這個盤算效果呢,看moveHandle.move.(moveHandle中有三個私有變量,moving鎖定句柄,防備動畫過程當中用戶再次滑動,win是不是成功,lost時刻失利)

move: function (arrow) { // arrow = 挪動方向
    if (this.moving) { // 假如正在舉行動畫,返回挪動失利
        return false
    }

    let result = this['move' + arrow]() // 獵取挪動盤算后的效果
    let newData = result.newData // 挪動后的數據矩陣
    let move = result.move // 挪動元素列表

    // 依據挪動元素列表推斷該操縱是不是有用
    if (move.length === 0) { // 沒有能夠挪動的元素,則無效
        console.log('本次挪動無效')
        return false
    }

    // 舉行0.3秒動畫
    data = newData // 修正全局數據矩陣
    createNewElement() // 製造新元素

    // 推斷遊戲輸贏
    this.win = isWin(newData)
    if (!this.win) {
        this.lost = isLost(newData, emptyList)
    }

    this.moving = true // 鎖定該事宜句柄


    setTimeout((function (self) {
        animate(move, arrow)
        return function () {
            // 充足時刻后
            self.moving = false // 停止動畫
            paint(container, data) // 重繪視圖

            // 推斷遊戲輸贏
            if (self.win) { // 贏得了遊戲
                win()
            } else if (self.lost) {
                lost()
            }
        }
    })(this), animateTime)
}

我自認為我的解釋內容照樣挺多的,應當照樣能看懂。此次分享就到這了。迎接批評區留言議論。發明有什麼bug也盡量跟我說把。

BUG

現在已知的是:
1.微信內置瀏覽器的轉碼題目:

這個由於我懶得整一個域名,所以它為了平安就會舉行轉碼,沒法遊戲。也就不修復了。。只是個小玩具。

2.ios長按會拔取筆墨而且沒法作廢:

這個題目我已做了肯定的修復,然則我沒復現這個題目的要領,也沒再處置懲罰

3.夸克瀏覽器自帶手勢致使左滑右滑會舉行體系行動:

沒想到方法,假如有人有方法請告訴我,感謝。
    原文作者:GDUTxxZ
    原文地址: https://segmentfault.com/a/1190000014439824
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞