基于react的经典2048游戏

react是一个强大的交互式UI渲染类库,非常适合一些对dom渲染频繁的场合,比如游戏。今天我来个大家简单介绍一下基于一个react的2048游戏,废话不多少直接开始
先感受一下效果


基于react的经典2048游戏_第1张图片
Animation.gif
  • 创建棋盘对象(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组

基于react的经典2048游戏_第2张图片
无标题.png
  • 当按方向键时,棋子分组
//  棋盘的分组函数
        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; }

现在大概的运行效果就有了,

基于react的经典2048游戏_第3张图片
Animation2.gif

添加计分功能和游戏结束功能先添加俩个函数

//  判断是否能移动
        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里面,其实没啥太大必要的

你可能感兴趣的:(基于react的经典2048游戏)