react是一个强大的交互式UI渲染类库,非常适合一些对dom渲染频繁的场合,比如游戏。今天我来个大家简单介绍一下基于一个react的2048游戏,废话不多少直接开始
先感受一下效果
- 创建棋盘对象(4x4)
var initial_board = {
a1:null,a2:null,a3:null,a4:null,
b1:null,b2:null,b3:null,b4:null,
c1:null,c2:null,c3:null,c4:null,
d1:null,d2:null,d3:null,d4:null
};
- 设置初始函数来判断哪个位置有棋子无棋子
// 获取所有无棋子的位置
function available_spaces(board){
return Object.keys(board).filter(function(key){
return board[key] === null
});
}
// 获取有棋子的位置
function used_spaces(board){
return Object.keys(board).filter(function(key){
return board[key] !== null
});
}
// 创建新的棋子
var tile_counter = 0;
function new_tile(initial) {
return {
id:tile_counter++,
values:[initial]//棋子的值是一个数组,两个棋子合拼时候,就会push新的数值,在页面上展示的最后一个数值
}
}
// 设置某一位置上的棋子的值
function set_tile(board, where, tile) {
var new_board = {};
Object.keys(board).forEach(function (key) {
new_board[key] = (key == where) ? tile : board[key]
});
return new_board
}
// 返回棋子的值
function tile_value(tile){
return tile ? tile.values[tile.values.length-1] : null;
}
当我们按下键盘右箭头→时,棋盘上的棋子分为4组
- 当按方向键时,棋子分组
// 棋盘的分组函数
function fold_order(xs, ys, reverse_keys){
return xs.map(function(x){
return ys.map(function(y){
var key = [x,y];
if(reverse_keys){
return key.reverse().join("");
}
return key.join("");
});
});
}
// 按左方向键时的分组
var left = fold_order(["a","b","c","d"], ["1","2","3","4"], false);
// 按右方向键时的分组
var right = fold_order(["a","b","c","d"], ["4","3","2","1"], false);
// 按上方向键时的分组
var up = fold_order(["1","2","3","4"], ["a","b","c","d"], true);
// 按下方向键时的分组
var down = fold_order( ["1","2","3","4"], ["d","c","b","a"], true);
- 处理整个棋盘
// 处理整个棋盘
function fold_board(board, lines){
//copy reference
var new_board = board;
lines.forEach(function(line){
var new_line = fold_line(board, line);//fold_line函数处理每行棋子
Object.keys(new_line).forEach(function(key){
//mutate reference while building up board
new_board = set_tile(new_board, key, new_line[key]);
});
});
return new_board;
}
// 处理棋盘的每行的棋子
function fold_line(board, line) {
var tiles = line.map(function(key){
return board[key];
}).filter(function(tile){
return tile !== null
});
var new_tiles = [];
if(tiles){
//must loop so we can skip next if matched
for(var i=0; i < tiles.length; i++){
var tile = tiles[i];
if(tile){
var val = tile_value(tile);
var next_tile = tiles[i+1];
if(next_tile && val == tile_value(next_tile)){
//skip next tile;
i++;
new_tiles.push({
id: next_tile.id, //keep id
values: tile.values.concat([val * 2])
});
}
else{
new_tiles.push(tile);
}
}
}
}
var new_line = {};
line.forEach(function(key, i){
new_line[key] = new_tiles[i] || null;
});
return new_line;
}
- 当我们渲染之后,我们要将原来的棋盘和处理过的棋盘进行比较,判断是否相同,方便起见使用函数调用
// 判断两个棋盘是否相等,
function same_board(board1, board2){
return Object.keys(board1).reduce(function(ret, key){
return ret && board1[key] == board2[key];
}, true);
}
- 开始创建一个Tiles组件,这个组件仅负责渲染棋盘
var Tiles = React.createClass({
render: function(){
var board = this.props.board;//这个组件只负责渲染棋盘
var tiles = used_spaces(board)
return {
tiles.map(function(key){
var tile = board[key];
var val = tile_value(tile);
return
{val}
;
})}
}
});
- 开始创建一个board组件,这个组件负责监听按键,创建棋子,改变棋盘,将新的棋盘交给Tiles组件处理
var GameBoard = React.createClass({
getInitialState: function(){ //初始创建2个棋子
return this.addTile(this.addTile(initial_board));
},
keyHandler:function(e){//通过按键,如果生成新的棋盘,则在100ms后添加一个新的棋子
var directions = { 37: left,38: up, 39: right, 40: down };
if(directions[e.keyCode]
&& this.setBoard(fold_board(this.state, directions[e.keyCode]))
){
setTimeout(function(){
this.setBoard(this.addTile(this.state));
}.bind(this), 100);
}
},
setBoard:function(new_board){//通过same_board判断新棋盘和旧棋盘是否一样,一样就设置新棋盘返回true,不一样直接返回false
if(!same_board(this.state, new_board)){
this.setState(new_board);
return true;
}
return false;
},
addTile:function(board){//添加棋子,值随机是2或4
var location = available_spaces(board).sort(function() {
return .5 - Math.random();
}).pop();
if(location){
var two_or_four = Math.floor(Math.random() * 2, 0) ? 2 : 4;
return set_tile(board, location, new_tile(two_or_four));
}
return board;
},
newGame:function(){//重新初始化棋盘
this.setState(this.getInitialState());
},
componentDidMount:function(){//对按键添加事件监听
window.addEventListener("keydown", this.keyHandler, false);
},
render:function(){
return
}
});
- 添加动画样式,
*{ margin: 0px; padding: 0px; }
.app{ margin:10px; font-family: arial; }
.board{
display:block;
position:relative;
margin:10px 0px 10px 0px;
border:1px solid #ccc;
width:215px;
height:215px;
padding:5px;
}
.board span{
font-family: arial;
letter-spacing: -1px;
display:block;
width:50px;
height:36px;
position:absolute;
text-align:center;
color:white;
font-weight:bold;
font-size:20px;
padding-top:14px;
background-color:#ebe76f;
border-radius: 5px;
transition: all 100ms linear;
}
.a1, .b1, .c1, .d1{ left:5px; }
.a2, .b2, .c2, .d2{ left:60px; }
.a3, .b3, .c3, .d3{ left:115px; }
.a4, .b4, .c4, .d4{ left:170px; }
.a1, .a2, .a3, .a4{ top:5px; }
.b1, .b2, .b3, .b4{ top:60px; }
.c1, .c2, .c3, .c4{ top:115px; }
.d1, .d2, .d3, .d4{ top:170px; }
span.value2{ background-color:#ebb26f; }
span.value4{ background-color:#ea6feb; }
span.value8{ background-color:#eb6fa3; }
span.value16{ background-color:#7a6feb; }
span.value32{ background-color:#af6feb; }
span.value64{ background-color:#6febcf; }
span.value128{ background-color:#6fbeeb; }
span.value256{ background-color:#afeb6f; }
span.value512{ background-color:#7aeb6f; }
span.value1024{ background-color:#e4eb6f; }
现在大概的运行效果就有了,
添加计分功能和游戏结束功能先添加俩个函数
// 判断是否能移动
function can_move(board){
var new_board = [up,down,left,right].reduce(function(b, direction){
return fold_board(b, direction);
}, board);
return available_spaces(new_board).length > 0
}
// 获取总分书
function score_board(board){
return used_spaces(board).map(function(key){
return (board[key].values.reduce(function(a, b) {
return a + b; //记录总分
})) - board[key].values[0]; //减去创建时的分数
}).reduce(function(a,b){return a+b}, 0);
}
- 之后在GameBoard组件中render函数修改
//GameBoard组件
render:function(){
var status = !can_move(this.state)?" - Game Over!":"";
return
Score: {score_board(this.state)}{status}//这里显示分数和游戏结束
}
一个原始2048小游戏的就基本出来,等有时间我再写个vue版的2048。工作中写页面难免有些无聊,平时多写一下小游戏锻炼一下思维也是挺好的。
--------------------------------------------------分割线---------------------------------------------------------
vue2的2048早就写完了,只是一直没时间上传,这里我直接列一下github地址:https://github.com/bfc846958672/vue2048
其实和react 差不多,只不过是一个组件,用v-for 根据棋盘数据 渲染dom,还有就是我把所有的方法都放到了vue里面的methods里面,其实没啥太大必要的