为何闲着没事要作一个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.夸克浏览器自带手势致使左滑右滑会进行系统行为:
没想到办法,若是有人有办法请告诉我,谢谢。