用 React 编写2048游戏
1.代码
1 <!DOCTYPE html> 2 <html lang="zh-cn"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>万能的React</title> 6 <style> 7 .app{ 8 margin:10px; 9 font-family: arial; 10 } 11 .board{ 12 display:block; 13 position:relative; 14 margin:10px 0px 10px 0px; 15 border:1px solid #ccc; 16 width:215px; 17 height:215px; 18 padding:5px; 19 } 20 .board span{ 21 font-family: arial; 22 letter-spacing: -1px; 23 display:block; 24 width:50px; 25 height:36px; 26 position:absolute; 27 text-align:center; 28 color:white; 29 font-weight:bold; 30 font-size:20px; 31 padding-top:14px; 32 background-color:#ebe76f; 33 border-radius: 5px; 34 transition: all 100ms linear; 35 } 36 .a1, .b1, .c1, .d1{ left:5px; } 37 .a2, .b2, .c2, .d2{ left:60px; } 38 .a3, .b3, .c3, .d3{ left:115px; } 39 .a4, .b4, .c4, .d4{ left:170px; } 40 .a1, .a2, .a3, .a4{ top:5px; } 41 .b1, .b2, .b3, .b4{ top:60px; } 42 .c1, .c2, .c3, .c4{ top:115px; } 43 .d1, .d2, .d3, .d4{ top:170px; } 44 span.value2{ background-color:#ebb26f; } 45 span.value4{ background-color:#ea6feb; } 46 span.value8{ background-color:#eb6fa3; } 47 span.value16{ background-color:#7a6feb; } 48 span.value32{ background-color:#af6feb; } 49 span.value64{ background-color:#6febcf; } 50 span.value128{ background-color:#6fbeeb; } 51 span.value256{ background-color:#afeb6f; } 52 span.value512{ background-color:#7aeb6f; } 53 span.value1024{ background-color:#e4eb6f; } 54 </style> 55 </head> 56 <body> 57 <script src="./react-0.13.2/react-0.13.2/build/react.js"></script> 58 <script src="./react-0.13.2/react-0.13.2/build/JSXTransformer.js"></script> 59 <script type="text/jsx"> 60 var initial_board = { 61 a1:null,a2:null,a3:null,a4:null, 62 b1:null,b2:null,b3:null,b4:null, 63 c1:null,c2:null,c3:null,c4:null, 64 d1:null,d2:null,d3:null,d4:null 65 }; 66 67 function available_spaces(board){ 68 return Object.keys(board).filter(function(key){ 69 return board[key] == null 70 }); 71 } 72 73 function used_spaces(board){ 74 return Object.keys(board).filter(function(key){ 75 return board[key] !== null 76 }); 77 } 78 79 function score_board(board){ 80 return used_spaces(board).map(function(key){ 81 return (board[key].values.reduce(function(a, b) { 82 return a + b; //sum tile values 83 })) - board[key].values[0]; //don't count initial value 84 }).reduce(function(a,b){return a+b}, 0); 85 } 86 87 function tile_value(tile){ 88 return tile ? tile.values[tile.values.length-1] : null; 89 } 90 91 function can_move(board){ 92 var new_board = [up,down,left,right].reduce(function(b, direction){ 93 return fold_board(b, direction); 94 }, board); 95 return available_spaces(new_board).length > 0 96 } 97 98 function same_board(board1, board2){ 99 return Object.keys(board1).reduce(function(ret, key){ 100 return ret && board1[key] == board2[key]; 101 }, true); 102 } 103 104 function fold_line(board, line){ 105 var tiles = line.map(function(key){ 106 return board[key]; 107 }).filter(function(tile){ 108 return tile !== null 109 }); 110 var new_tiles = []; 111 if(tiles){ 112 //must loop so we can skip next if matched 113 for(var i=0; i < tiles.length; i++){ 114 var tile = tiles[i]; 115 if(tile){ 116 var val = tile_value(tile); 117 var next_tile = tiles[i+1]; 118 if(next_tile && val == tile_value(next_tile)){ 119 //skip next tile; 120 i++; 121 new_tiles.push({ 122 id: next_tile.id, //keep id 123 values: tile.values.concat([val * 2]) 124 }); 125 } 126 else{ 127 new_tiles.push(tile); 128 } 129 } 130 } 131 } 132 var new_line = {}; 133 line.forEach(function(key, i){ 134 new_line[key] = new_tiles[i] || null; 135 }); 136 return new_line; 137 } 138 139 function fold_order(xs, ys, reverse_keys){ 140 return xs.map(function(x){ 141 return ys.map(function(y){ 142 var key = [x,y]; 143 if(reverse_keys){ 144 return key.reverse().join(""); 145 } 146 return key.join(""); 147 }); 148 }); 149 } 150 151 function fold_board(board, lines){ 152 //copy reference 153 var new_board = board; 154 lines.forEach(function(line){ 155 var new_line = fold_line(board, line); 156 Object.keys(new_line).forEach(function(key){ 157 //mutate reference while building up board 158 new_board = set_tile(new_board, key, new_line[key]); 159 }); 160 }); 161 return new_board; 162 } 163 164 var tile_counter = 0; 165 function new_tile(initial){ 166 return { 167 id: tile_counter++, 168 values: [initial] 169 }; 170 } 171 172 function set_tile(board, where, tile){ 173 //do not destory the old board 174 var new_board = {}; 175 Object.keys(board).forEach(function(key, i){ 176 //copy by reference for structual sharing 177 new_board[key] = (key == where) ? tile : board[key]; 178 }); 179 return new_board; 180 } 181 182 var left = fold_order(["a","b","c","d"], ["1","2","3","4"], false); 183 var right = fold_order(["a","b","c","d"], ["4","3","2","1"], false); 184 var up = fold_order(["1","2","3","4"], ["a","b","c","d"], true); 185 var down = fold_order( ["1","2","3","4"], ["d","c","b","a"], true); 186 187 var GameBoard = React.createClass({ 188 getInitialState: function(){ 189 return this.addTile(this.addTile(initial_board)); 190 }, 191 keyHandler:function(e){ 192 var directions = { 193 37: left, 194 38: up, 195 39: right, 196 40: down 197 }; 198 if(directions[e.keyCode] 199 && this.setBoard(fold_board(this.state, directions[e.keyCode])) 200 && Math.floor(Math.random() * 30, 0) > 0){ 201 setTimeout(function(){ 202 this.setBoard(this.addTile(this.state)); 203 }.bind(this), 100); 204 } 205 }, 206 setBoard:function(new_board){ 207 if(!same_board(this.state, new_board)){ 208 this.setState(new_board); 209 return true; 210 } 211 return false; 212 }, 213 addTile:function(board){ 214 var location = available_spaces(board).sort(function() { 215 return .5 - Math.random(); 216 }).pop(); 217 if(location){ 218 var two_or_four = Math.floor(Math.random() * 2, 0) ? 2 : 4; 219 return set_tile(board, location, new_tile(two_or_four)); 220 } 221 return board; 222 }, 223 newGame:function(){ 224 this.setState(this.getInitialState()); 225 }, 226 componentDidMount:function(){ 227 window.addEventListener("keydown", this.keyHandler, false); 228 }, 229 render:function(){ 230 var status = !can_move(this.state)?" - Game Over!":""; 231 return <div className="app"> 232 <span className="score"> 233 Score: {score_board(this.state)}{status} 234 </span> 235 <Tiles board={this.state}/> 236 <button onClick={this.newGame}>New Game</button> 237 </div> 238 } 239 }); 240 241 var Tiles = React.createClass({ 242 render: function(){ 243 var board = this.props.board; 244 //sort board keys first to stop re-ordering of DOM elements 245 var tiles = used_spaces(board).sort(function(a, b) { 246 return board[a].id - board[b].id; 247 }); 248 return <div className="board">{ 249 tiles.map(function(key){ 250 var tile = board[key]; 251 var val = tile_value(tile); 252 return <span key={tile.id} className={key + " value" + val}> 253 {val} 254 </span>; 255 })}</div> 256 } 257 }); 258 259 React.render(<GameBoard />, document.body); 260 </script> 261 </body> 262 </html>
2.结果
You can do anything you set your mind to, man!