前言
最近学习Vue和webpack,刚好搞个小游戏练练手。
2048游戏规则:
每次可以选择上下左右其中一个方向去滑动,每滑动一次,所有的数字方块都会往滑动的方向靠拢外,系统也会在空白的地方乱数出现一个数字方块,相同数字的方块在靠拢、相撞时会相加。不断的叠加最终拼凑出2048这个数字就算成功。
当然有些细微的合并规则,比如:
当向左滑动时,某列2 2 2 2 合并成 4 4 0 0 而非 8 0 0 0
也就是说,同列的某个数字最多只被合并一次。
在线挑战一把?(瞎折腾一阵后发现移动端无法正常显示了,赶紧调试去)
http://www.ccc5.cc/2048/
移动端扫下面的二维码即可(微信会转码,请点击扫描后底部的‘访问原网页’)
思路
4x4的方格,用一个16个成员的数组表示。当某个方格没有数字的时候用''表示;
创建Vue的模板,绑定数据,处理数据与相关class的关系;
把数组当作一个4x4的矩阵,专注数据方面的处理,Dom方面的就交给vue更新
模板
其中getclass
与getposition
为自定义指令:
getclass
根据当前框数字设置不同的classgetposition
根据当前框的索引位置,设置css样式的top
与left
关键实现
初始化数据
初始化一个长度为16的数组,然后随机选两个地方填入2或者4。
这里有必要说明下,在segmentfault看到很多人洗牌算法习惯这么写:
var arr = arr.sort(_=> {
return Math.random() - 0.5
});
但是经过很多人的测试,这样洗牌其实是不太乱的,具体参考
数组的完全随机排列:https://www.h5jun.com/post/ar...
如何测试洗牌程序:http://coolshell.cn/articles/...
继续回到主题,数据初始化完成之后,添加两个自定义指令,功能前面已经讲过了,实现也很简单,
方格里面数字不同,对应的class不一样,我这里定义的规则是
数字2对应.s2
数字4对应.s4
...
事件处理
监听4个方向键和移动端的滑动事件,在ready环节处理
ready: function () {
document.addEventListener('keyup', this.keyDown);
document.querySelector('#app ul').addEventListener('touchstart', this.touchStart);
document.querySelector('#app ul').addEventListener('touchend', this.touchEnd);
//document上获取touchmove事件 如果是由.box触发的 则禁止屏幕滚动
document.addEventListener('touchmove', e=>{
e.target.classList.contains('box') && e.preventDefault();
});
},
methods:{
touchStart(e){
//在start中记录开始触摸的点
this.start['x'] = e.changedTouches[0].pageX;
this.start['y'] = e.changedTouches[0].pageY;
},
touchEnd(e){
//handle...
},
/*
*方向键 事件处理
*/
keyDown(e){
//handle...
}
}
我们将nums这个数组想象成一个4x4的方阵。
2 2 2 2
x x x x
x x x x
x x x x
当向左
合并的时候,以第一列来说,从左至右:
inxde=0位置的2在合并时是不需要动的,index=1位置的2和index=0位置的2数值相同,合并成4,放在index=0的位置,第一列变成
4 '' 2 2
index=2位置的2,向左挪到index=1空出的位置,变成
4 2 '' 2
,同时,index=3位置的2一直向左运动,直到碰上index=1处的2,二者在这轮都没有过合并,所以这里可以合并为4 4 '' ''
从这里我们可以看出,向左
运动时,最左的位置,也就是index%4 === 0
的位置,是无需挪动的,其他位置,若有数字,其左侧有空位的话则向左挪动一位,其左侧有个与其相同的数字时,且二者在此轮中没有合并过,则二者合并,空出当前位:
2 2 2 2
=>4 4 '' ''
4 2 2 2
=>4 4 2 ''
'' 4 2 2
=>4 4 '' ''
2 2 4 2
=>4 4 2 ''
...
如果打算将向上,向右,向下都这么处理一遍并非不可以,但比较容易出错。我们既然都将nums想象成了一个4x4的方阵,那么何不将该方阵旋转一下呢。
当向下
运动时,我们先将nums利用算法将想象中的方阵顺时针旋转一次,然后可以用向左
运动处理的方法合并、移动方格,完毕后再顺时针旋转3次,或者逆时针旋转1次还原即可。
旋转算法:
详细的方向转换及处理过程
当所有的16个方格都已经被填满,且横向与纵向都无法合并时,游戏结束
isPass(){
let isOver=true,hasPass=false,tmp = this.T(this.nums,1);
this.nums.forEach((i,j)=>{
if(this.nums[j-4] == i || this.nums[j+4] == i || tmp[j-4] == tmp[j] || tmp[j+4] == tmp[j]){
isOver = false;
}
if(i==2048){
hasPass = true;
}
});
if(!this.blankIndex().length){
isOver && alert('游戏结束!');
};
}
。。。。。。。。
如何才能PASS?你有本事可以玩到很恐怖的数字。具体能玩到多少需要用数学证明吧,已有知乎大神证明,估计在3884450左右。传送门https://www.zhihu.com/questio...
代码
奉上全部代码app.vue
index.html
2048 Game
main.js
import Vue from 'vue'
import App from './app.vue'
new Vue({
el: 'body',
components:{App}
});
webpack配置
const webpack = require('webpack');
module.exports = {
entry: {
app:["./app/main.js",'webpack-hot-middleware/client','webpack/hot/dev-server']
},
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js",//打包后输出文件的文件名
publicPath:'http://localhost:8080/'
},
module: {
loaders: [
{test: /\.css$/, loader: 'style!css'},
{test: /\.vue$/, loader: 'vue'},
{test: /\.js$/,exclude:/node_modules|vue\/dist|vue-router\/|vue-loader\/|vue-hot-reload-api\// ,loader: 'babel'}
]
},
vue:{
loaders:{
js:'babel'
}
},
babel: {
presets: ['es2015','stage-0'],
plugins: ['transform-runtime']
},
plugins:[
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录
colors: true,//终端中输出结果为彩色
hot: true,
inline: true//实时刷新
},
resolve: {
extensions: ['', '.js', '.vue']
}
}
css就不贴了,Github上托管了:https://github.com/Elity/2048...
很久没写这么长的文章了,累死。花了整整俩小时!!!