mirror of
https://github.com/zhigang1992/react-native-web.git
synced 2026-04-30 13:21:39 +08:00
351 lines
7.4 KiB
JavaScript
351 lines
7.4 KiB
JavaScript
/**
|
|
* 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';
|
|
|
|
import { any, func, object } from 'prop-types';
|
|
import GameBoard from './GameBoard';
|
|
import React from 'react';
|
|
import { Animated, AppRegistry, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
|
const BOARD_PADDING = 3;
|
|
const CELL_MARGIN = 4;
|
|
const CELL_SIZE = 60;
|
|
|
|
class Cell extends React.Component {
|
|
render() {
|
|
return <View style={styles.cell} />;
|
|
}
|
|
}
|
|
|
|
class Board extends React.Component {
|
|
static propTypes = {
|
|
children: any
|
|
};
|
|
|
|
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 propTypes = {
|
|
tile: object
|
|
};
|
|
|
|
static _getPosition(index): number {
|
|
return BOARD_PADDING + (index * (CELL_SIZE + CELL_MARGIN * 2) + CELL_MARGIN);
|
|
}
|
|
|
|
constructor(props: {}) {
|
|
super(props);
|
|
|
|
const 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 } {
|
|
const tile = this.props.tile;
|
|
|
|
const 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() {
|
|
const tile = this.props.tile;
|
|
|
|
const tileStyles = [styles.tile, styles['tile' + tile.value], this.calculateOffset()];
|
|
|
|
const 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 {
|
|
static propTypes = {
|
|
board: object,
|
|
onRestart: func
|
|
};
|
|
|
|
render() {
|
|
const board = this.props.board;
|
|
|
|
if (!board.hasWon() && !board.hasLost()) {
|
|
return <View />;
|
|
}
|
|
|
|
const message = board.hasWon() ? 'Good Job!' : 'Game Over';
|
|
|
|
return (
|
|
<View style={styles.overlay}>
|
|
<Text style={styles.overlayMessage}>
|
|
{message}
|
|
</Text>
|
|
<TouchableOpacity onPress={this.props.onRestart} style={styles.tryAgain}>
|
|
<Text style={styles.tryAgainText}>Try Again?</Text>
|
|
</TouchableOpacity>
|
|
</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;
|
|
}
|
|
|
|
_handleRestart = () => {
|
|
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;
|
|
}
|
|
|
|
const deltaX = event.nativeEvent.pageX - this.startX;
|
|
const deltaY = event.nativeEvent.pageY - this.startY;
|
|
|
|
let 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() {
|
|
const tiles = this.state.board.tiles
|
|
.filter(tile => tile.value)
|
|
.map(tile => <Tile key={tile.id} ref={tile.id} tile={tile} />);
|
|
|
|
return (
|
|
<View
|
|
onTouchEnd={this._handleTouchEnd}
|
|
onTouchStart={this._handleTouchStart}
|
|
style={styles.container}
|
|
>
|
|
<Board>
|
|
{tiles}
|
|
</Board>
|
|
<GameEndOverlay board={this.state.board} onRestart={this._handleRestart} />
|
|
</View>
|
|
);
|
|
}
|
|
}
|
|
|
|
const 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: {
|
|
width: CELL_SIZE,
|
|
height: CELL_SIZE,
|
|
borderRadius: 5,
|
|
backgroundColor: '#ddccbb',
|
|
margin: CELL_MARGIN
|
|
},
|
|
row: {
|
|
flexDirection: 'row'
|
|
},
|
|
tile: {
|
|
position: 'absolute',
|
|
width: 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);
|
|
|
|
export default Game2048;
|