爲何閒着沒事要作一個2048呢?還不是360前端星計劃(2018春招實習生)要我作的。而後就花了幾天時間作了一個2048小遊戲,兼容到pc端和部分移動端(設備有限,有的移動瀏覽器真的沒兼容到或者是真的不想作兼容了)。僅供你們看看就好哈。css
github地址: https://github.com/GDUTxxZ/20...html
在線預覽:http://47.94.199.75/index.html (這個網址暫時有效。。之後點進去不知道又是個人什麼實驗做品。)前端
我作完給朋友看以後發現不是每一個人都玩過這個遊戲。簡單介紹一下游戲內容好了。ios
獲勝條件: 拼湊出一個2048方塊
失敗條件: 當前沒有可用方塊,而且全部方塊都不能夠和臨近方塊合併git
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 失敗消息或者勝利消息github
css的話,主要是關於動畫元素的設置:web
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也儘量跟我說把。
目前已知的是:
1.微信內置瀏覽器的轉碼問題:
這個由於我懶得整一個域名,因此它爲了安全就會進行轉碼,無法遊戲。也就不修復了。。只是個小玩具。
2.ios長按會選取文字並且沒法取消:
這個問題我已經作了必定的修復,可是我沒復現這個問題的方法,也沒再處理
3.夸克瀏覽器自帶手勢致使左滑右滑會進行系統行爲:
沒想到辦法,若是有人有辦法請告訴我,謝謝。