不多说,直接上代码
<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 {
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 {
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 {
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;
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