GameBoard.js
/** * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * * Facebook reserves all rights not expressly granted. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @providesModule GameBoard * @flow */ 'use strict'; // NB: Taken straight from: https://github.com/IvanVergiliev/2048-react/blob/master/src/board.js // with no modification except to format it for CommonJS and fix lint/flow errors var rotateLeft = function (matrix) { var rows = matrix.length; var columns = matrix[0].length; var res = []; for (var row = 0; row < rows; ++row) { res.push([]); for (var column = 0; column < columns; ++column) { res[row][column] = matrix[column][columns - row - 1]; } } return res; }; var Tile = function (value?: number, row?: number, column?: number) { this.value = value || 0; this.row = row || -1; this.column = column || -1; this.oldRow = -1; this.oldColumn = -1; this.markForDeletion = false; this.mergedInto = null; this.id = Tile.id++; }; Tile.id = 0; Tile.prototype.moveTo = function (row, column) { this.oldRow = this.row; this.oldColumn = this.column; this.row = row; this.column = column; }; Tile.prototype.isNew = function () { return this.oldRow === -1 && !this.mergedInto; }; Tile.prototype.hasMoved = function () { return (this.fromRow() !== -1 && (this.fromRow() !== this.toRow() || this.fromColumn() !== this.toColumn())) || this.mergedInto; }; Tile.prototype.fromRow = function () { return this.mergedInto ? this.row : this.oldRow; }; Tile.prototype.fromColumn = function () { return this.mergedInto ? this.column : this.oldColumn; }; Tile.prototype.toRow = function () { return this.mergedInto ? this.mergedInto.row : this.row; }; Tile.prototype.toColumn = function () { return this.mergedInto ? this.mergedInto.column : this.column; }; var Board = function () { this.tiles = []; this.cells = []; for (var i = 0; i < Board.size; ++i) { this.cells[i] = [this.addTile(), this.addTile(), this.addTile(), this.addTile()]; } this.addRandomTile(); this.setPositions(); this.won = false; }; Board.prototype.addTile = function () { var res = new Tile(); Tile.apply(res, arguments); this.tiles.push(res); return res; }; Board.size = 4; Board.prototype.moveLeft = function () { var hasChanged = false; for (var row = 0; row < Board.size; ++row) { var currentRow = this.cells[row].filter(function (tile) { return tile.value !== 0; }); var resultRow = []; for (var target = 0; target < Board.size; ++target) { var targetTile = currentRow.length ? currentRow.shift() : this.addTile(); if (currentRow.length > 0 && currentRow[0].value === targetTile.value) { var tile1 = targetTile; targetTile = this.addTile(targetTile.value); tile1.mergedInto = targetTile; var tile2 = currentRow.shift(); tile2.mergedInto = targetTile; targetTile.value += tile2.value; } resultRow[target] = targetTile; this.won = this.won || (targetTile.value === 2048); hasChanged = hasChanged || (targetTile.value !== this.cells[row][target].value); } this.cells[row] = resultRow; } return hasChanged; }; Board.prototype.setPositions = function () { this.cells.forEach(function (row, rowIndex) { row.forEach(function (tile, columnIndex) { tile.oldRow = tile.row; tile.oldColumn = tile.column; tile.row = rowIndex; tile.column = columnIndex; tile.markForDeletion = false; }); }); }; Board.fourProbability = 0.1; Board.prototype.addRandomTile = function () { var emptyCells = []; for (var r = 0; r < Board.size; ++r) { for (var c = 0; c < Board.size; ++c) { if (this.cells[r][c].value === 0) { emptyCells.push({r: r, c: c}); } } } var index = Math.floor(Math.random() * emptyCells.length); var cell = emptyCells[index]; var newValue = Math.random() < Board.fourProbability ? 4 : 2; this.cells[cell.r][cell.c] = this.addTile(newValue); }; Board.prototype.move = function (direction) { // 0 -> left, 1 -> up, 2 -> right, 3 -> down this.clearOldTiles(); for (var i = 0; i < direction; ++i) { this.cells = rotateLeft(this.cells); } var hasChanged = this.moveLeft(); for (var i = direction; i < 4; ++i) { this.cells = rotateLeft(this.cells); } if (hasChanged) { this.addRandomTile(); } this.setPositions(); return this; }; Board.prototype.clearOldTiles = function () { this.tiles = this.tiles.filter(function (tile) { return tile.markForDeletion === false; }); this.tiles.forEach(function (tile) { tile.markForDeletion = true; }); }; Board.prototype.hasWon = function () { return this.won; }; Board.deltaX = [-1, 0, 1, 0]; Board.deltaY = [0, -1, 0, 1]; Board.prototype.hasLost = function () { var canMove = false; for (var row = 0; row < Board.size; ++row) { for (var column = 0; column < Board.size; ++column) { canMove = canMove || (this.cells[row][column].value === 0); for (var dir = 0; dir < 4; ++dir) { var newRow = row + Board.deltaX[dir]; var newColumn = column + Board.deltaY[dir]; if (newRow < 0 || newRow >= Board.size || newColumn < 0 || newColumn >= Board.size) { continue; } canMove = canMove || (this.cells[row][column].value === this.cells[newRow][newColumn].value); } } } return !canMove; }; module.exports = Board;
index.android.js
/** * The examples provided by Facebook are for non-commercial testing and * evaluation purposes only. * * Facebook reserves all rights not expressly granted. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * @providesModule Game2048 * @flow */ 'use strict'; var React = require('react-native'); var { AppRegistry, StyleSheet, Text, View, } = React; var Animated = require('Animated'); var GameBoard = require('GameBoard'); var TouchableBounce = require('TouchableBounce'); var BOARD_PADDING = 3; var CELL_MARGIN = 4; var CELL_SIZE = 60; class Cell extends React.Component { render() { return <View style={styles.cell} />; } } class Board extends React.Component { render() { return ( <View style={styles.board}> <View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View> <View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View> <View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View> <View style={styles.row}><Cell/><Cell/><Cell/><Cell/></View> {this.props.children} </View> ); } } class Tile extends React.Component { state: any; static _getPosition(index): number { return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN); } constructor(props: {}) { super(props); var tile = this.props.tile; this.state = { opacity: new Animated.Value(0), top: new Animated.Value(Tile._getPosition(tile.toRow())), left: new Animated.Value(Tile._getPosition(tile.toColumn())), }; } calculateOffset(): {top: number; left: number; opacity: number} { var tile = this.props.tile; var offset = { top: this.state.top, left: this.state.left, opacity: this.state.opacity, }; if (tile.isNew()) { Animated.timing(this.state.opacity, { duration: 100, toValue: 1, }).start(); } else { Animated.parallel([ Animated.timing(offset.top, { duration: 100, toValue: Tile._getPosition(tile.toRow()), }), Animated.timing(offset.left, { duration: 100, toValue: Tile._getPosition(tile.toColumn()), }), ]).start(); } return offset; } render() { var tile = this.props.tile; var tileStyles = [ styles.tile, styles['tile' + tile.value], this.calculateOffset(), ]; var textStyles = [ styles.value, tile.value > 4 && styles.whiteText, tile.value > 100 && styles.threeDigits, tile.value > 1000 && styles.fourDigits, ]; return ( <Animated.View style={tileStyles}> <Text style={textStyles}>{tile.value}</Text> </Animated.View> ); } } class GameEndOverlay extends React.Component { render() { var board = this.props.board; if (!board.hasWon() && !board.hasLost()) { return <View/>; } var message = board.hasWon() ? '恭喜你顺利成为第930315个祝樊菲生日快乐的人!' : 'Game Over'; return ( <View style={styles.overlay}> <Text style={styles.overlayMessage}>{message}</Text> <TouchableBounce onPress={this.props.onRestart} style={styles.tryAgain}> <Text style={styles.tryAgainText}>Try Again?</Text> </TouchableBounce> </View> ); } } class Game2048 extends React.Component { startX: number; startY: number; state: any; constructor(props: {}) { super(props); this.state = { board: new GameBoard(), }; this.startX = 0; this.startY = 0; } restartGame() { this.setState({board: new GameBoard()}); } handleTouchStart(event: Object) { if (this.state.board.hasWon()) { return; } this.startX = event.nativeEvent.pageX; this.startY = event.nativeEvent.pageY; } handleTouchEnd(event: Object) { if (this.state.board.hasWon()) { return; } var deltaX = event.nativeEvent.pageX - this.startX; var deltaY = event.nativeEvent.pageY - this.startY; var direction = -1; if (Math.abs(deltaX) > 3 * Math.abs(deltaY) && Math.abs(deltaX) > 30) { direction = deltaX > 0 ? 2 : 0; } else if (Math.abs(deltaY) > 3 * Math.abs(deltaX) && Math.abs(deltaY) > 30) { direction = deltaY > 0 ? 3 : 1; } if (direction !== -1) { this.setState({board: this.state.board.move(direction)}); } } render() { var tiles = this.state.board.tiles .filter((tile) => tile.value) .map((tile) => <Tile ref={tile.id} key={tile.id} tile={tile} />); return ( <View style={styles.container} onTouchStart={(event) => this.handleTouchStart(event)} onTouchEnd={(event) => this.handleTouchEnd(event)}> <Board> {tiles} </Board> <GameEndOverlay board={this.state.board} onRestart={() => this.restartGame()} /> </View> ); } } var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', }, board: { padding: BOARD_PADDING, backgroundColor: '#bbaaaa', borderRadius: 5, }, overlay: { position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, backgroundColor: 'rgba(221, 221, 221, 0.5)', flex: 1, flexDirection: 'column', justifyContent: 'center', alignItems: 'center', }, overlayMessage: { fontSize: 40, marginBottom: 20, }, tryAgain: { backgroundColor: '#887761', padding: 20, borderRadius: 5, }, tryAgainText: { color: '#ffffff', fontSize: 20, fontWeight: '500', }, cell: { CELL_SIZE, height: CELL_SIZE, borderRadius: 5, backgroundColor: '#ddccbb', margin: CELL_MARGIN, }, row: { flexDirection: 'row', }, tile: { position: 'absolute', CELL_SIZE, height: CELL_SIZE, backgroundColor: '#ddccbb', borderRadius: 5, flex: 1, justifyContent: 'center', alignItems: 'center', }, value: { fontSize: 24, color: '#776666', fontFamily: 'Verdana', fontWeight: '500', }, tile2: { backgroundColor: '#eeeeee', }, tile4: { backgroundColor: '#eeeecc', }, tile8: { backgroundColor: '#ffbb87', }, tile16: { backgroundColor: '#ff9966', }, tile32: { backgroundColor: '#ff7755', }, tile64: { backgroundColor: '#ff5533', }, tile128: { backgroundColor: '#eecc77', }, tile256: { backgroundColor: '#eecc66', }, tile512: { backgroundColor: '#eecc55', }, tile1024: { backgroundColor: '#eecc33', }, tile2048: { backgroundColor: '#eecc22', }, whiteText: { color: '#ffffff', }, threeDigits: { fontSize: 20, }, fourDigits: { fontSize: 18, }, }); AppRegistry.registerComponent('Game2048', () => Game2048); module.exports = Game2048;