不多说,直接上代码
<template> <div class="gameMain"> <div class="gameName">2048小游戏</div> <div class="maxScore"> 最高分:<span id="maxScore">{{ maxScore }}</span> </div> <div class="col-sm-3 col-md-4"></div> <div class="gameBody col-sm-6 col-md-4" id="gameBody" v-touch:left="move" v-touch:right="move" v-touch:up="move" v-touch:down="move"> <div class="row" v-for="(row, index) in gameList" :key="index"> <div class="item" :style="{ background: refreshColorData[item.num] }" v-for="(item, idx) in row" :key="idx" > {{ item ? item.num : null }} </div> </div> </div> <div class="col-sm-4 col-md-4 gameDirection"> <span @click="move('up')">上</span> <span @click="move('down')">下</span> <span @click="move('left')">左</span> <span @click="move('right')">右</span> </div> <div class="scoreAndRefresh col-sm-6 col-md-6"> <div class="gameScore "> 得分:<span id="gameScore">{{ gameScore }}</span> 分 </div> <div class="btn btn-danger refreshBtn" @click="refreshGame">刷新</div> </div> <div class="gameOver" v-if="gameOver">游戏结束</div> </div> </template> <script> import touch from './directives.js' export default { name: 'Games', directives: { touch }, data () { return { refreshColorData: { 2: 'rgb(250, 225, 188)', 4: 'rgb(202, 240, 240)', 8: 'rgb(117, 231, 193)', 16: 'rgb(240, 132, 132)', 32: 'rgb(181, 240, 181)', 64: 'rgb(182, 210, 246)', 128: 'rgb(255, 207, 126)', 256: 'rgb(250, 216, 216)', 521: 'rgb(124, 183, 231)', 1024: 'rgb(225, 219, 215)', 2048: 'rgb(221, 160, 221)', 4096: 'rgb(250, 139, 176)' }, gameOver: false, gameScore: 0, maxScore: 0, // 最高分 gameList: null, isNewRndItem: false // // 是否产生新元素 } }, created () { this.gameList = this.matrix(4, 4, null) // 游戏初始化 this.gameInit() }, methods: { gameInit () { // 初始化分数 this.gameScore = 0 this.gameScore = 0 // 最大分值 if (localStorage.getItem('maxScore')) { this.maxScore = localStorage.getItem('maxScore') - 0 } else { this.maxScore = 0 } // 随机生成两个新元素 this.newRndItem() this.newRndItem() }, move (direction) { if (this.gameOver) return false // 获取所有非空元素 let nonEmptyItems = [].concat .apply([], this.gameList) .filter(item => item.num !== null) // 如果按下的方向是左或上,则正向遍历非空元素 if (direction === 'left' || direction === 'up') { for (let i = 0; i < nonEmptyItems.length; i++) { let currentItem = nonEmptyItems[i] this.itemMove(currentItem, direction) } } else if (direction === 'right' || direction === 'down') { // 如果按下的方向是右或下,则反向遍历非空元素 for (let i = nonEmptyItems.length - 1; i >= 0; i--) { let currentItem = nonEmptyItems[i] this.itemMove(currentItem, direction) } } // 是否产生新元素 if (this.isNewRndItem && !this.gameOver) { this.newRndItem() } this.isGameOver() }, getSideItem (current, direction) { let sideItemX = current.id.substr(0, 1) let sideItemY = current.id.slice(1, 2) let falg switch (direction) { case 'left': falg = sideItemX > 0 sideItemX = falg ? Number(sideItemX) - 1 : sideItemX break case 'right': falg = sideItemX < 3 sideItemX = falg ? Number(sideItemX) + 1 : sideItemX break case 'up': falg = sideItemY > 0 sideItemY = falg ? Number(sideItemY) - 1 : sideItemY break case 'down': falg = sideItemY < 3 sideItemY = falg ? Number(sideItemY) + 1 : sideItemY break } let currentId = sideItemX + sideItemY let currentItem = falg ? [].concat(...this.gameList).filter(item => item.id === currentId)[0] : null // 判断移动方向是否有空位 return currentItem }, itemMove (currentItem, direction) { var sideItem = this.getSideItem(currentItem, direction) // 当前元素在最边上 if (sideItem === null) return false // 当前元素不在最后一个且左(右、上、下)侧元素是空元素 if (sideItem.num === null) { this.setGameList(sideItem, currentItem.num) sideItem.num = currentItem.num currentItem.num = null this.itemMove(sideItem, direction) this.isNewRndItem = true } else if (sideItem.num === currentItem.num) { sideItem.num = Number(currentItem.num) * 2 currentItem.num = null this.gameScore += Number(sideItem.num) * 10 this.maxScore = this.maxScore < this.gameScore ? this.gameScore : this.maxScore localStorage.setItem('maxScore', this.maxScore) this.itemMove(sideItem, direction) this.isNewRndItem = true } }, // 游戏是否结束 isGameOver () { let nonEmptyItems = [].concat .apply([], this.gameList) .filter(item => item.num !== null) let Items = [].concat .apply([], this.gameList) let gameOver = true if (Items.length === nonEmptyItems.length) { // 所有元素的个数 == 所有非空元素的个数 即没有空元素 nonEmptyItems.forEach(currentItem => { // let up = this.getSideItem(currentItem, 'up') && this.getSideItem(currentItem, 'up').num // let down = this.getSideItem(currentItem, 'down') && this.getSideItem(currentItem, 'down').num // let left = this.getSideItem(currentItem, 'left') && this.getSideItem(currentItem, 'left').num // let right = this.getSideItem(currentItem, 'right') && this.getSideItem(currentItem, 'right').num // console.log(up + 'up' + down + 'down' + left + 'left' + right + 'right') // alert(currentItem.num + 'up' + this.getSideItem(currentItem, 'up').num + 'down' + this.getSideItem(currentItem, 'down').num + 'left' + this.getSideItem(currentItem, 'left').num + 'right' + this.getSideItem(currentItem, 'right').num) if (this.getSideItem(currentItem, 'up') && currentItem.num === this.getSideItem(currentItem, 'up').num) { gameOver = false } else if (this.getSideItem(currentItem, 'down') && currentItem.num === this.getSideItem(currentItem, 'down').num) { gameOver = false } else if (this.getSideItem(currentItem, 'left') && currentItem.num === this.getSideItem(currentItem, 'left').num) { gameOver = false } else if (this.getSideItem(currentItem, 'right') && currentItem.num === this.getSideItem(currentItem, 'right').num) { gameOver = false } }) } else { gameOver = false } this.gameOver = gameOver }, // 随机生成新数字 newRndItem () { var newRndArr = [2, 2, 4] var newRndNum = newRndArr[this.getRandom(0, 2)] let emptyItemList = [].concat .apply([], this.gameList) .filter(item => item.num === null) var newRndSite = this.getRandom(0, emptyItemList.length - 1) var emptyItem = emptyItemList[newRndSite] this.setGameList(emptyItem, newRndNum) }, // 设置数字 setGameList (item, num) { if (!item) return false for (var row = 0; row < this.gameList.length; ++row) { for (var col = 0; col < this.gameList[row].length; ++col) { if (this.gameList[row][col].id === item.id) { this.gameList[row][col].num = num } } } }, // 产生随机数,包括min、max getRandom (min, max) { return min + Math.floor(Math.random() * (max - min + 1)) }, // 刷新操作 refreshGame () { this.gameList = this.matrix(4, 4, null) this.gameOver = false // 游戏初始化 this.gameInit() }, // 随机生成一个两位数组 matrix (numrows, numcols, initial) { var arr = [] for (var i = 0; i < numrows; ++i) { var columns = [] for (var j = 0; j < numcols; ++j) { columns[j] = { id: j + '' + i, num: initial } // columns[j] = initial } arr[i] = columns } return arr } } } </script> <style scoped lang="scss"> // @import "./index.scss"; .gameMain { height: calc(100vh - 88px); font-size: 28px; background: #d7d3b6; .gameName { font-size: 28px; font-weight: bold; padding-top: 20px; } .maxScore { font-size: 38px; margin: 20px auto; span { color: red; font-weight: bold; } } .gameBody { width: 80%; height: 50%; margin: 0 auto; display: flex; flex-direction: column; justify-content: space-between; padding: 10px; background: #999; border-radius: 8px; padding-top: 5px; padding-bottom: 5px; .row { display: flex; justify-content: space-between; .item { width: 100px; height: 100px; border-radius: 10px; background: #fff; text-align: center; line-height: 100px; font-size: 30px; font-weight: bold; margin: 5px; color: #666; } } } .gameDirection { margin: 50px auto; font-size: 26px; font-weight: bold; span { width: 100px; display: inline-block; } } .gameRule { font-size: 26px; font-weight: bold; margin-top: 5px; } .gameScore { font-size: 20px; font-weight: bold; line-height: 40px; span { color: red; font-size: 30px; } } .scoreAndRefresh { display: flex; justify-content: space-around; align-items: center; width: 280px; margin: 20px auto; .refreshBtn { padding:10px 20px; line-height: 40px; margin-top: 8px; background: #093233; color: #fff; border-radius: 6px; } } .gameOver{ color: red; font-size: 40px; } } </style>
注释
v-touch:left,v-touch:right,v-touch:up,v-touch:down
使用vue自定义指令
const touch = { bind (el, binding, vnode) { console.log(binding) // 滑动指令 var touchType = binding.arg // 传入的模式 press swipeRight swipeLeft swipeTop swipeDowm Tap var timeOutEvent = 0 var direction = '' // 滑动处理 var startX, startY // 返回角度 function GetSlideAngle (dx, dy) { return Math.atan2(dy, dx) * 180 / Math.PI } // 根据起点和终点返回方向 1:向上,2:向下,3:向左,4:向右,0:未滑动 function GetSlideDirection (startX, startY, endX, endY) { var dy = startY - endY var dx = endX - startX var result = 0 // 如果滑动距离太短 if (Math.abs(dx) < 2 && Math.abs(dy) < 2) { return result } var angle = GetSlideAngle(dx, dy) if (angle >= -45 && angle < 45) { result = 'right' } else if (angle >= 45 && angle < 135) { result = 'up' } else if (angle >= -135 && angle < -45) { result = 'down' } else if ((angle >= 135 && angle <= 180) || (angle >= -180 && angle < -135)) { result = 'left' } return result } el.addEventListener('touchstart', function (ev) { startX = ev.touches[0].pageX startY = ev.touches[0].pageY // 判断长按 timeOutEvent = setTimeout(() => { timeOutEvent = 0 if (touchType === 'press') { binding.value() } }, 500) }, false) el.addEventListener('touchmove', function (ev) { clearTimeout(timeOutEvent) timeOutEvent = 0 }) el.addEventListener('touchend', function (ev) { var endX, endY endX = ev.changedTouches[0].pageX endY = ev.changedTouches[0].pageY direction = GetSlideDirection(startX, startY, endX, endY) clearTimeout(timeOutEvent) switch (direction) { case 0: break case 'up': if (touchType === 'up') { binding.value(direction) } break case 'down': if (touchType === 'down') { binding.value(direction) } break case 'left': if (touchType === 'left') { binding.value(direction) } break case 'right': if (touchType === 'right') { binding.value(direction) } break default: } }, false) } } export default touch