整体流程以下所示css
command (keyCode) { // 总部
this.WhetherToRotate(keyCode) // 是否须要将上下操做转换为左右操做
this.Init() // 数据初始化 合并数字
this.IfInvalid() // 判断是否无效
this.Rendering(keyCode) // 渲染到页面
}
复制代码
首先先将基本的 HTML 标签跟 CSS 样式写出来html
因为用的 vue ,因此渲染 html 部分的代码不用咱们去手写vue
<template>
<div id='app'>
<div class='total'>总分: {{this.total}} 分</div> // {{}} 这个中间表示 JavaScript 表达式
<div class='main'>
<div class='row' v-for='(items,index) of arr' :key='index'> // v-for表示循环渲染当前元素,具体渲染次数为 arr.length
<div
:class='`c-${item} item`'
v-for='(item,index) of items'
:key='index'
>{{item>0?item:''}}</div> // :class= 表示将 JavaScript 变量做为类名
</div>
</div>
<footer>
<h2>玩法说明:</h2>
<p>1.用键盘上下左右键控制数字走向</p>
<p>2.当点击了一个方向时,格子中的数字会所有往那个方向移动,直到不能再移动,若是有相同的数字则会合并</p>
<p>3.当格子中再也不有可移动和可合并的数字时,游戏结束</p>
</footer>
</div>
</template>
复制代码
css因为太长就不放了跟以前基本没有太多区别git
接下来是数据的初始化github
data () {
return {
arr: [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], // 与页面绑定的数组
Copyarr: [[], [], [], []], // 用来数据操做的数组
initData: [], // 包含数字详细坐标的数组
haveGrouping: false, // 有能够合并的数字
itIsLeft: false, // 是否为向左合并,默认不是向左合并
endGap: true, // 判断最边上有没有空隙 默认有空隙
middleGap: true, // 真 为某行中间有空隙
haveZero: true, // 当前页面有没有 0
total: 0, // 总分数
itIs2048: false, // 是否成功
max: 2048 // 最高分数
}
}
复制代码
在 mounted 添加事件监听bash
为何在 mounted 添加事件? 咱们先了解下vue的生命周期app
因此若是太早的话可能找不到 dom 节点,太晚的话,可能不能第一时间进行事件的响应框架
mounted () {
window.onkeydown = e => {
switch (e.keyCode) {
case 37:
// ←
console.log('←')
this.Command(e.keyCode)
break
case 38:
// ↑
console.log('↑')
this.Command(e.keyCode)
break
case 39:
// →
this.Command(e.keyCode)
console.log('→')
break
case 40:
// ↓
console.log('↓')
this.Command(e.keyCode)
break
}
}
}
复制代码
这段代码我是某天半梦半醒想到的,可能思惟很差转过来,能够看看代码下面的图dom
这样一来就将向上的操做转换成了向左的操做
向下的操做就转换成了向右的操做
这样折腾下能够少写一半的数字合并代码
WhetherToRotate (keyCode) { // 是否须要将上下操做转换为左右操做
if (keyCode === 38 || keyCode === 40) { // 38 是上 40 是下
this.Copyarr = this.ToRotate(this.arr)
} else if (keyCode === 37 || keyCode === 39) { // 37 是左 39 是右
[...this.Copyarr] = this.arr
}
// 将当前操做作一个标识
if (keyCode === 37 || keyCode === 38) { // 数据转换后只有左右操做
this.itIsLeft = true
} else if (keyCode === 39 || keyCode === 40) {
this.itIsLeft = false
}
}
复制代码
转换代码
ToRotate (arr) { // 将数据从 x 到 y y 到 x 相互转换
let afterCopyingArr = [[], [], [], []]
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr[i].length; j++) {
afterCopyingArr[i][j] = arr[j][i]
}
}
return afterCopyingArr
}
复制代码
Init () { // 数据初始化
this.initData = this.DataDetails() // 非零数字详情
this.Copyarr = this.NumberMerger() // 数字合并
}
复制代码
IfInvalid () { // 判断是否无效
// 判断每行中间有没有空隙
this.MiddleGap() // 真 为某行中间有空隙
this.EndPointGap() // 在没有中间空隙的条件下去判断最边上有没有空隙
}
复制代码
MiddleGap () { // 检查每行中间有没有空隙
// 当全部的数都是挨着的,那么 x 下标两两相减并除以组数获得的绝对数是 1 ,比他大说明中间有空隙
// 先将 x 下标两两相减 并添加到新的数组
let subarr = [[], [], [], []] // 两两相减的数据
let sumarr = [] // 处理后的最终数据
this.initData.forEach((items, index) => {
items.forEach((item, i) => {
if (typeof items[i + 1] !== 'undefined') {
subarr[index].push(item.col - items[i + 1].col)
}
})
})
// 将每一行的结果相加获得总和 而后除以每一行结果的长度
subarr.forEach((items) => {
sumarr.push(items.reduceRight((a, b) => a + b, 0))
})
sumarr = sumarr.map((item, index) => Math.abs(item / subarr[index].length))
// 最后判断有没有比 1 大的值
sumarr.some(item => item > 1)
this.middleGap = sumarr.some(item => item > 1) // 真 为 有中间空隙
}
复制代码
EndPointGap () { // 检查最边上有没有空隙
// 判断是向左仍是向右 由于左右的判断是不同的
this.endGap = true
let end
let initData = this.initData
if (this.itIsLeft) {
end = 0
this.endGap = initData.some(items => items.length !== 0 ? items[0].col !== end : false)
} else {
end = 3
this.endGap = initData.some(items => items.length !== 0 ? items[items.length - 1].col !== end : false)
}
// 取出每行的第一个数的 x 下标
// 判断是否是最边上
// 有不是的 说明边上 至少有一个空隙
// 是的话说明边上没有空隙
}
复制代码
这样就将基本的判断是否有效,是否失败的条件都获得了
至因而否有可合并数字已经在数据初始化时就获得了
Rendering (keyCode) {
this.AddZero() // 先将占位符加上
// 由于以前的数据都处理好了 因此只须要将上下的数据转换回去就行了
if (keyCode === 38 || keyCode === 40) { // 38 是上 40 是下
this.Copyarr = this.ToRotate(this.Copyarr)
}
if (this.haveGrouping || this.endGap || this.middleGap) { // 知足任一条件就说明能够新建随机数字
this.RandomlyCreate(this.Copyarr)
} else if (this.haveZero) {
// 都不知足 可是有空位不作失败判断
} else {
// 以上都不知足视为没有空位,不可合并
if (this.itIs2048) { // 判断是否达成2048
this.RandomlyCreate(this.Copyarr)
alert('恭喜达成2048!')
// 下面注释掉的可以让游戏在点击弹框按钮后从新开始新游戏
// this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
// this.RandomlyCreate(this.arr)
} else { //以上都不知足视为失败
this.RandomlyCreate(this.Copyarr)
alert('游戏结束!')
// this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
// this.RandomlyCreate(this.arr)
}
}
if (this.itIs2048) { // 每次页面渲染完,都判断是否达成2048
this.RandomlyCreate(this.Copyarr)
alert('恭喜达成2048!')
// this.arr = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
// this.RandomlyCreate(this.arr)
}
}
复制代码
这里以前是用递归函数的形式去判断,可是用递归函数的话会有不少问题,最大的问题就是可能会堆栈溢出,或者卡死(递归函数就是在函数的最后还会去调用本身,若是不给出 return 的条件,很容易堆栈溢出或卡死)
因此此次改为抽奖的模式,将全部的空位的坐标取到,放入一个数组,而后取这个数组的随机下标,这样咱们会获得一个空位的坐标,而后再对这个空位进行处理
RandomlyCreate (Copyarr) { // 随机空白处建立新数字
// 判断有没有能够新建的地方
let max = this.max
let copyarr = Copyarr
let zero = [] // 作一个抽奖的箱子
let subscript = 0 // 作一个拿到的奖品号
let number = 0 // 奖品号兑换的物品
// 找到全部的 0 将下标添加到新的数组
copyarr.forEach((items, index) => {
items.forEach((item, i) => {
if (item === 0) {
zero.push({ x: index, y: i })
}
})
})
// 取随机数 而后在空白坐标集合中找到它
subscript = Math.floor(Math.random() * zero.length)
if (Math.floor(Math.random() * 10) % 3 === 0) {
number = 4 // 三分之一的机会
} else {
number = 2 // 三分之二的机会
}
if (zero.length) {
Copyarr[zero[subscript].x][zero[subscript].y] = number
this.arr = Copyarr
}
this.total = 0
this.arr.forEach(items => {
items.forEach(item => {
if (item === max && !this.itIs2048) {
this.itIs2048 = true
}
this.total += item
})
})
}
复制代码
以上就是本次 2048 的主要代码 最后,由于随机出现4的概率我改的比较大,因此相应的下降了一些难度,具体体如今当全部数字都在左边(最边上),且数字与数字间没有空隙,再按左也会生成数字