forked from vergnet/application-amicale
Improved game UI and state management
This commit is contained in:
parent
8fc5cfb25e
commit
7f33c8376d
3 changed files with 162 additions and 32 deletions
|
@ -10,6 +10,7 @@ export default class GameLogic {
|
||||||
width: number;
|
width: number;
|
||||||
|
|
||||||
gameRunning: boolean;
|
gameRunning: boolean;
|
||||||
|
gamePaused: boolean;
|
||||||
gameTime: number;
|
gameTime: number;
|
||||||
score: number;
|
score: number;
|
||||||
|
|
||||||
|
@ -27,6 +28,7 @@ export default class GameLogic {
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.gameRunning = false;
|
this.gameRunning = false;
|
||||||
|
this.gamePaused = false;
|
||||||
this.gameTick = 250;
|
this.gameTick = 250;
|
||||||
this.colors = colors;
|
this.colors = colors;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +45,10 @@ export default class GameLogic {
|
||||||
return this.gameRunning;
|
return this.gameRunning;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isGamePaused(): boolean {
|
||||||
|
return this.gamePaused;
|
||||||
|
}
|
||||||
|
|
||||||
getEmptyLine() {
|
getEmptyLine() {
|
||||||
let line = [];
|
let line = [];
|
||||||
for (let col = 0; col < this.getWidth(); col++) {
|
for (let col = 0; col < this.getWidth(); col++) {
|
||||||
|
@ -161,17 +167,30 @@ export default class GameLogic {
|
||||||
callback(this.gameTime, this.score, this.getFinalGrid());
|
callback(this.gameTime, this.score, this.getFinalGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canUseInput() {
|
||||||
|
return this.gameRunning && !this.gamePaused
|
||||||
|
}
|
||||||
|
|
||||||
rightPressed(callback: Function) {
|
rightPressed(callback: Function) {
|
||||||
|
if (!this.canUseInput())
|
||||||
|
return;
|
||||||
|
|
||||||
this.tryMoveTetromino(1, 0);
|
this.tryMoveTetromino(1, 0);
|
||||||
callback(this.getFinalGrid());
|
callback(this.getFinalGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
leftPressed(callback: Function) {
|
leftPressed(callback: Function) {
|
||||||
|
if (!this.canUseInput())
|
||||||
|
return;
|
||||||
|
|
||||||
this.tryMoveTetromino(-1, 0);
|
this.tryMoveTetromino(-1, 0);
|
||||||
callback(this.getFinalGrid());
|
callback(this.getFinalGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
rotatePressed(callback: Function) {
|
rotatePressed(callback: Function) {
|
||||||
|
if (!this.canUseInput())
|
||||||
|
return;
|
||||||
|
|
||||||
this.tryRotateTetromino();
|
this.tryRotateTetromino();
|
||||||
callback(this.getFinalGrid());
|
callback(this.getFinalGrid());
|
||||||
}
|
}
|
||||||
|
@ -180,20 +199,32 @@ export default class GameLogic {
|
||||||
let shape = Math.floor(Math.random() * 7);
|
let shape = Math.floor(Math.random() * 7);
|
||||||
this.currentObject = new Tetromino(shape, this.colors);
|
this.currentObject = new Tetromino(shape, this.colors);
|
||||||
if (!this.isTetrominoPositionValid())
|
if (!this.isTetrominoPositionValid())
|
||||||
this.endGame();
|
this.endGame(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
endGame() {
|
togglePause() {
|
||||||
console.log('Game Over!');
|
if (!this.gameRunning)
|
||||||
|
return;
|
||||||
|
this.gamePaused = !this.gamePaused;
|
||||||
|
if (this.gamePaused) {
|
||||||
|
clearInterval(this.gameTickInterval);
|
||||||
|
} else {
|
||||||
|
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endGame(isRestart: boolean) {
|
||||||
this.gameRunning = false;
|
this.gameRunning = false;
|
||||||
|
this.gamePaused = false;
|
||||||
clearInterval(this.gameTickInterval);
|
clearInterval(this.gameTickInterval);
|
||||||
this.endCallback(this.gameTime, this.score);
|
this.endCallback(this.gameTime, this.score, isRestart);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(tickCallback: Function, endCallback: Function) {
|
startGame(tickCallback: Function, endCallback: Function) {
|
||||||
if (this.gameRunning)
|
if (this.gameRunning)
|
||||||
return;
|
this.endGame(true);
|
||||||
this.gameRunning = true;
|
this.gameRunning = true;
|
||||||
|
this.gamePaused = false;
|
||||||
this.gameTime = 0;
|
this.gameTime = 0;
|
||||||
this.score = 0;
|
this.score = 0;
|
||||||
this.currentGrid = this.getEmptyGrid();
|
this.currentGrid = this.getEmptyGrid();
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from 'react-native';
|
import {Alert, View} from 'react-native';
|
||||||
import {IconButton, Text, withTheme} from 'react-native-paper';
|
import {IconButton, Text, withTheme} from 'react-native-paper';
|
||||||
|
import {MaterialCommunityIcons} from "@expo/vector-icons";
|
||||||
import GameLogic from "./GameLogic";
|
import GameLogic from "./GameLogic";
|
||||||
import Grid from "./components/Grid";
|
import Grid from "./components/Grid";
|
||||||
|
import HeaderButton from "../../components/HeaderButton";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
navigation: Object,
|
navigation: Object,
|
||||||
|
@ -12,6 +14,7 @@ type Props = {
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
grid: Array<Array<Object>>,
|
grid: Array<Array<Object>>,
|
||||||
|
gameRunning: boolean,
|
||||||
gameTime: number,
|
gameTime: number,
|
||||||
gameScore: number
|
gameScore: number
|
||||||
}
|
}
|
||||||
|
@ -31,22 +34,49 @@ class TetrisScreen extends React.Component<Props, State> {
|
||||||
this.logic = new GameLogic(20, 10, this.colors);
|
this.logic = new GameLogic(20, 10, this.colors);
|
||||||
this.state = {
|
this.state = {
|
||||||
grid: this.logic.getEmptyGrid(),
|
grid: this.logic.getEmptyGrid(),
|
||||||
|
gameRunning: false,
|
||||||
gameTime: 0,
|
gameTime: 0,
|
||||||
gameScore: 0,
|
gameScore: 0,
|
||||||
};
|
};
|
||||||
this.onTick = this.onTick.bind(this);
|
this.onTick = this.onTick.bind(this);
|
||||||
this.onGameEnd = this.onGameEnd.bind(this);
|
this.onGameEnd = this.onGameEnd.bind(this);
|
||||||
this.updateGrid = this.updateGrid.bind(this);
|
this.updateGrid = this.updateGrid.bind(this);
|
||||||
const onScreenBlur = this.onScreenBlur.bind(this);
|
this.props.navigation.addListener('blur', this.onScreenBlur.bind(this));
|
||||||
this.props.navigation.addListener('blur', onScreenBlur);
|
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const rightButton = this.getRightButton.bind(this);
|
||||||
|
this.props.navigation.setOptions({
|
||||||
|
headerRight: rightButton,
|
||||||
|
});
|
||||||
|
this.startGame();
|
||||||
|
}
|
||||||
|
|
||||||
|
getRightButton() {
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}>
|
||||||
|
<HeaderButton icon={'pause'} onPress={() => this.togglePause()}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove any interval on un-focus
|
* Remove any interval on un-focus
|
||||||
*/
|
*/
|
||||||
onScreenBlur() {
|
onScreenBlur() {
|
||||||
this.logic.endGame();
|
if (!this.logic.isGamePaused())
|
||||||
|
this.logic.togglePause();
|
||||||
|
}
|
||||||
|
|
||||||
|
onScreenFocus() {
|
||||||
|
if (!this.logic.isGameRunning())
|
||||||
|
this.startGame();
|
||||||
|
else if (this.logic.isGamePaused())
|
||||||
|
this.showPausePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTick(time: number, score: number, newGrid: Array<Array<Object>>) {
|
onTick(time: number, score: number, newGrid: Array<Array<Object>>) {
|
||||||
|
@ -63,17 +93,63 @@ class TetrisScreen extends React.Component<Props, State> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame() {
|
togglePause() {
|
||||||
if (!this.logic.isGameRunning()) {
|
this.logic.togglePause();
|
||||||
this.logic.startGame(this.onTick, this.onGameEnd);
|
if (this.logic.isGamePaused())
|
||||||
}
|
this.showPausePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
onGameEnd(time: number, score: number) {
|
showPausePopup() {
|
||||||
|
Alert.alert(
|
||||||
|
'PAUSE',
|
||||||
|
'GAME PAUSED',
|
||||||
|
[
|
||||||
|
{text: 'RESTART', onPress: () => this.showRestartConfirm()},
|
||||||
|
{text: 'RESUME', onPress: () => this.togglePause()},
|
||||||
|
],
|
||||||
|
{cancelable: false},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showRestartConfirm() {
|
||||||
|
Alert.alert(
|
||||||
|
'RESTART?',
|
||||||
|
'WHOA THERE',
|
||||||
|
[
|
||||||
|
{text: 'NO', onPress: () => this.showPausePopup()},
|
||||||
|
{text: 'YES', onPress: () => this.startGame()},
|
||||||
|
],
|
||||||
|
{cancelable: false},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showGameOverConfirm() {
|
||||||
|
Alert.alert(
|
||||||
|
'GAME OVER',
|
||||||
|
'NOOB',
|
||||||
|
[
|
||||||
|
{text: 'LEAVE', onPress: () => this.props.navigation.goBack()},
|
||||||
|
{text: 'RESTART', onPress: () => this.startGame()},
|
||||||
|
],
|
||||||
|
{cancelable: false},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
startGame() {
|
||||||
|
this.logic.startGame(this.onTick, this.onGameEnd);
|
||||||
|
this.setState({
|
||||||
|
gameRunning: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onGameEnd(time: number, score: number, isRestart: boolean) {
|
||||||
this.setState({
|
this.setState({
|
||||||
gameTime: time,
|
gameTime: time,
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
})
|
gameRunning: false,
|
||||||
|
});
|
||||||
|
if (!isRestart)
|
||||||
|
this.showGameOverConfirm();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -82,28 +158,49 @@ class TetrisScreen extends React.Component<Props, State> {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
<View style={{
|
||||||
textAlign: 'center',
|
flexDirection: 'row',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
left: 10,
|
||||||
}}>
|
}}>
|
||||||
Score: {this.state.gameScore}
|
<MaterialCommunityIcons
|
||||||
</Text>
|
name={'timer'}
|
||||||
<Text style={{
|
color={this.colors.subtitle}
|
||||||
textAlign: 'center',
|
size={20}/>
|
||||||
|
<Text style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
color: this.colors.subtitle
|
||||||
|
}}>{this.state.gameTime}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginLeft: 'auto',
|
||||||
}}>
|
}}>
|
||||||
time: {this.state.gameTime}
|
<MaterialCommunityIcons
|
||||||
</Text>
|
name={'star'}
|
||||||
|
color={this.colors.tetrisScore}
|
||||||
|
size={30}/>
|
||||||
|
<Text style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
fontSize: 22,
|
||||||
|
}}>{this.state.gameScore}</Text>
|
||||||
|
</View>
|
||||||
<Grid
|
<Grid
|
||||||
width={this.logic.getWidth()}
|
width={this.logic.getWidth()}
|
||||||
height={this.logic.getHeight()}
|
height={this.logic.getHeight()}
|
||||||
grid={this.state.grid}
|
grid={this.state.grid}
|
||||||
/>
|
/>
|
||||||
<View style={{
|
<View style={{
|
||||||
flexDirection: 'row-reverse',
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
}}>
|
}}>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="arrow-right"
|
icon="format-rotate-90"
|
||||||
size={40}
|
size={40}
|
||||||
onPress={() => this.logic.rightPressed(this.updateGrid)}
|
onPress={() => this.logic.rotatePressed(this.updateGrid)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="arrow-left"
|
icon="arrow-left"
|
||||||
|
@ -111,15 +208,15 @@ class TetrisScreen extends React.Component<Props, State> {
|
||||||
onPress={() => this.logic.leftPressed(this.updateGrid)}
|
onPress={() => this.logic.leftPressed(this.updateGrid)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="format-rotate-90"
|
icon="arrow-right"
|
||||||
size={40}
|
size={40}
|
||||||
onPress={() => this.logic.rotatePressed(this.updateGrid)}
|
onPress={() => this.logic.rightPressed(this.updateGrid)}
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="power"
|
icon="arrow-down"
|
||||||
size={40}
|
size={40}
|
||||||
onPress={() => this.startGame()}
|
onPress={() => this.logic.rightPressed(this.updateGrid)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
@ -58,6 +58,7 @@ export default class ThemeManager {
|
||||||
// Tetris
|
// Tetris
|
||||||
tetrisBackground:'#d1d1d1',
|
tetrisBackground:'#d1d1d1',
|
||||||
tetrisBorder:'#afafaf',
|
tetrisBorder:'#afafaf',
|
||||||
|
tetrisScore:'#fff307',
|
||||||
tetrisI : '#42f1ff',
|
tetrisI : '#42f1ff',
|
||||||
tetrisO : '#ffdd00',
|
tetrisO : '#ffdd00',
|
||||||
tetrisT : '#ba19ff',
|
tetrisT : '#ba19ff',
|
||||||
|
@ -109,6 +110,7 @@ export default class ThemeManager {
|
||||||
// Tetris
|
// Tetris
|
||||||
tetrisBackground:'#2c2c2c',
|
tetrisBackground:'#2c2c2c',
|
||||||
tetrisBorder:'#1b1b1b',
|
tetrisBorder:'#1b1b1b',
|
||||||
|
tetrisScore:'#e2d707',
|
||||||
tetrisI : '#30b3be',
|
tetrisI : '#30b3be',
|
||||||
tetrisO : '#c1a700',
|
tetrisO : '#c1a700',
|
||||||
tetrisT : '#9114c7',
|
tetrisT : '#9114c7',
|
||||||
|
|
Loading…
Reference in a new issue