数组方法css
数学方法html
整体流程以下所示vue
command (keyCode) { // 总部 this.WhetherToRotate(keyCode) // 是否须要将上下操做转换为左右操做 this.Init() // 数据初始化 合并数字 this.IfInvalid() // 判断是否无效 this.Rendering(keyCode) // 渲染到页面 }
首先先将基本的 HTML 标签跟 CSS 样式写出来git
因为用的 vue ,因此渲染 html 部分的代码不用咱们去手写github
<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因为太长就不放了跟以前基本没有太多区别数组
接下来是数据的初始化app
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 添加事件监听 dom
为何在 mounted 添加事件?
咱们先了解下vue的生命周期函数
因此若是太早的话可能找不到 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 } } }
这段代码我是某天半梦半醒想到的,可能思惟很差转过来,能够看看代码下面的图
这样一来就将向上的操做转换成了向左的操做
向下的操做就转换成了向右的操做
这样折腾下能够少写一半的数字合并代码
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的概率我改的比较大,因此相应的下降了一些难度,具体体如今当全部数字都在左边(最边上),且数字与数字间没有空隙,再按左也会生成数字