用Vue.js简单实现2048

2048是一个喜闻乐见的游戏,最近在看 Vue,于是想用 Vue 来实现一下,结果发现并没有用到 Vue 里面的很多内容。

游戏面板构建

首先,构造一个 4 * 4 的游戏面板,很简单的用 ulli 来实现就可以了。

1
2
3
4
5
6
7
<ul id="game-board">
<li v-for="block in blocks"
track-by="$index"
v-text="block == 0 ? '' : block"
:class="{'empty': block == 0}">
</li>
</ul>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default {
data() {
return {
blocks: [] // 储存16个格子里的数值,将在组件初始化的时候进行填充。
}
},
methods: {
render() {
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
},
ready() {
this.render();
}
}
}
1
/*css样式我就不详细贴出来了,因为没有什么特别的。*/

上面的代码应该一目了然吧,在生命周期方法 ready 里进行游戏面板的构造,当然,现在这个面板是空的,接下来需要……

游戏初始化

在2048里,游戏的初始状态是在16个格子中随机填充数字2或4,当然出现2的概率会比出现4的概率大,本着一切就简的原则,这里我们出现4的概率是0,哈哈哈。

这个和 Vue 也没什么关系,主要是涉及到随机数的生成,在 javascript 里用 Math.random() 方法生成随机数,在这里有个公式挺有用的,要取整数 n 到 m 之间的随机数可以用这样的公式:

1
var num = Math.floor(Math.random() * (m - n)) + n;

知道了这点,我们来把代码补全:

1
2
3
4
5
6
7
8
9
10
11
12
render() {
this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
// 游戏初始化, 将游戏面板中随机两块设置为2
var first = Math.floor(Math.random() * 15);
var second;
do {
second = Math.floor(Math.random() * 15);
} while(first == second);
this.blocks.$set(first, 2);
this.blocks.$set(second, 2);
}

这里用了一个 do...while 来保证生成的两个数是不相同的。

加入事件

本来我想偷个懒,直接用键盘的上下左右来控制事件,但是整页响应键盘事件要挂载在 document 对象上,太丑了。最后还有决定用手势事件来做,这里就用到了 vue-touch 这个库,用法很简单。

第一步当然是:

1
npm i vue-touch --save

安装好了之后和用 Vue.use() 来挂载一下。

1
2
import VueTouch from 'vue-touch';
Vue.use(VueTouch);

然后就可以在组件中使用 v-touch 这个指令了。

先把事件绑好:

1
2
3
4
5
6
<ul id="game-board" v-touch:swipeleft="moveLeft"
v-touch:swipeup="moveUp"
v-touch:swiperight="moveRight"
v-touch:swipedown="moveDown">
...
</ul>

逻辑实现

2048的逻辑十分简单,只要理清思路就很好做,之前我在 iOS 上也做过一次,上次是用多维数组实现的,这次用一维数组实现我觉得到是更简单了……

开始来写向左滑动的逻辑,先理一下思路好了:

  1. 向左滑动实际上有可能产生滑动的是右边的三列,所以我们可以不考虑左边第一列
  2. 滑动停止的条件:
    • 遇到边框
    • 遇到一个非空格子并且和里面的数字和自己的不一样
  3. 如果遇到和自己一样的数字,就合并。
  4. 所有格子都移动完之后,在游戏面板的空格子里添加一个数字2。

思路就是这样,下面看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
moveLeft() {
for(let i = 0; i < this.blocks.length; i++) {
var moveTo = i;
if(i % 4 == 0) continue; // 只操作后面三列
// 寻找目标位置
while(moveTo % 4 != 0) { // 遇到边框
if(this.blocks[moveTo - 1] != 0 && // 遇到一个非空格子
this.blocks[moveTo - 1] != this.blocks[i]) //并且数字和自己的不同。
break;
moveTo--;
}
this.move(i, moveTo); // 移动事件
}
this.newBlock(); // 添加新数字块事件
}
move(from, to) {
if(from == to) return;
if (this.blocks[from] == this.blocks[to]) {
this.blocks.$set(to, this.blocks[to] * 2);
} else {
this.blocks.$set(to, this.blocks[from]);
}
this.blocks.$set(from, 0);
}
newBlock() {
var newBlock;
do {
newBlock = Math.floor(Math.random() * 15);
} while(this.blocks[newBlock] != 0);
this.blocks.$set(newBlock, 2);
}

可以看到真正的移动其实是在 move() 方法里面事件的, moveLeft() 方法里面只是获取到这个数字块需要移动到的位置。这样 move() 方法还可以被向其他方向的方法来公用。

好了,向左的写好了,其他方向的也就大同小异了。但是有几个需要注意的地方:

  1. 上下格子的 index 相差4
  2. 向右和向下运动方法里面遍历数组的时候应该从后往前遍历,不然会有不可预料的结果。

恩,就写到这里吧,只是一个简单的实现,没有加入分数计算和游戏结束的判断,可能明天来写一下,也有可能就不写了,因为我实在是太懒了。

搞了半天和 Vue 没什么关系,哈哈哈。