application-amicale/src/screens/Game/screens/GameMainScreen.tsx
2020-09-22 23:13:09 +02:00

472 lines
12 KiB
TypeScript

/*
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
*
* This file is part of Campus INSAT.
*
* Campus INSAT is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Campus INSAT is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
import * as React from 'react';
import {View} from 'react-native';
import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import i18n from 'i18n-js';
import {StackNavigationProp} from '@react-navigation/stack';
import GameLogic from '../logic/GameLogic';
import type {GridType} from '../components/GridComponent';
import GridComponent from '../components/GridComponent';
import Preview from '../components/Preview';
import MaterialHeaderButtons, {
Item,
} from '../../../components/Overrides/CustomHeaderButton';
import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
type PropsType = {
navigation: StackNavigationProp<any>;
route: {params: {highScore: number}};
theme: ReactNativePaper.Theme;
};
type StateType = {
grid: GridType;
gameTime: number;
gameScore: number;
gameLevel: number;
dialogVisible: boolean;
dialogTitle: string;
dialogMessage: string;
dialogButtons: Array<OptionsDialogButtonType>;
onDialogDismiss: () => void;
};
class GameMainScreen extends React.Component<PropsType, StateType> {
static getFormattedTime(seconds: number): string {
const date = new Date();
date.setHours(0);
date.setMinutes(0);
date.setSeconds(seconds);
let format;
if (date.getHours()) {
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
} else if (date.getMinutes()) {
format = `${date.getMinutes()}:${date.getSeconds()}`;
} else {
format = date.getSeconds().toString();
}
return format;
}
logic: GameLogic;
highScore: number | null;
constructor(props: PropsType) {
super(props);
this.highScore = null;
this.logic = new GameLogic(20, 10, props.theme);
this.state = {
grid: this.logic.getCurrentGrid(),
gameTime: 0,
gameScore: 0,
gameLevel: 0,
dialogVisible: false,
dialogTitle: '',
dialogMessage: '',
dialogButtons: [],
onDialogDismiss: () => {},
};
if (props.route.params != null) {
this.highScore = props.route.params.highScore;
}
}
componentDidMount() {
const {navigation} = this.props;
navigation.setOptions({
headerRight: this.getRightButton,
});
this.startGame();
}
componentWillUnmount() {
this.logic.endGame(true);
}
getRightButton = () => {
return (
<MaterialHeaderButtons>
<Item title="pause" iconName="pause" onPress={this.togglePause} />
</MaterialHeaderButtons>
);
};
onTick = (score: number, level: number, newGrid: GridType) => {
this.setState({
gameScore: score,
gameLevel: level,
grid: newGrid,
});
};
onClock = (time: number) => {
this.setState({
gameTime: time,
});
};
onDialogDismiss = () => {
this.setState({dialogVisible: false});
};
onGameEnd = (time: number, score: number, isRestart: boolean) => {
const {props, state} = this;
this.setState({
gameTime: time,
gameScore: score,
});
if (!isRestart) {
props.navigation.replace('game-start', {
score: state.gameScore,
level: state.gameLevel,
time: state.gameTime,
});
}
};
getStatusIcons() {
const {props, state} = this;
return (
<View
style={{
flex: 1,
marginTop: 'auto',
marginBottom: 'auto',
}}>
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
}}>
<Caption
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 5,
}}>
{i18n.t('screens.game.time')}
</Caption>
<View
style={{
flexDirection: 'row',
}}>
<MaterialCommunityIcons
name="timer"
color={props.theme.colors.subtitle}
size={20}
/>
<Text
style={{
marginLeft: 5,
color: props.theme.colors.subtitle,
}}>
{GameMainScreen.getFormattedTime(state.gameTime)}
</Text>
</View>
</View>
<View
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 20,
}}>
<Caption
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginBottom: 5,
}}>
{i18n.t('screens.game.level')}
</Caption>
<View
style={{
flexDirection: 'row',
}}>
<MaterialCommunityIcons
name="gamepad-square"
color={props.theme.colors.text}
size={20}
/>
<Text
style={{
marginLeft: 5,
}}>
{state.gameLevel}
</Text>
</View>
</View>
</View>
);
}
getScoreIcon() {
const {props, state} = this;
const highScore =
this.highScore == null || state.gameScore > this.highScore
? state.gameScore
: this.highScore;
return (
<View
style={{
marginTop: 10,
marginBottom: 10,
}}>
<View
style={{
flexDirection: 'row',
marginLeft: 'auto',
marginRight: 'auto',
}}>
<Text
style={{
marginLeft: 5,
fontSize: 20,
}}>
{i18n.t('screens.game.score', {score: state.gameScore})}
</Text>
<MaterialCommunityIcons
name="star"
color={props.theme.colors.tetrisScore}
size={20}
style={{
marginTop: 'auto',
marginBottom: 'auto',
marginLeft: 5,
}}
/>
</View>
<View
style={{
flexDirection: 'row',
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 5,
}}>
<Text
style={{
marginLeft: 5,
fontSize: 10,
color: props.theme.colors.textDisabled,
}}>
{i18n.t('screens.game.highScore', {score: highScore})}
</Text>
<MaterialCommunityIcons
name="star"
color={props.theme.colors.tetrisScore}
size={10}
style={{
marginTop: 'auto',
marginBottom: 'auto',
marginLeft: 5,
}}
/>
</View>
</View>
);
}
getControlButtons() {
const {props} = this;
return (
<View
style={{
height: 80,
flexDirection: 'row',
}}>
<IconButton
icon="rotate-right-variant"
size={40}
onPress={() => {
this.logic.rotatePressed(this.updateGrid);
}}
style={{flex: 1}}
/>
<View
style={{
flexDirection: 'row',
flex: 4,
}}>
<IconButton
icon="chevron-left"
size={40}
style={{flex: 1}}
onPress={() => {
this.logic.pressedOut();
}}
onPressIn={() => {
this.logic.leftPressedIn(this.updateGrid);
}}
/>
<IconButton
icon="chevron-right"
size={40}
style={{flex: 1}}
onPress={() => {
this.logic.pressedOut();
}}
onPressIn={() => {
this.logic.rightPressed(this.updateGrid);
}}
/>
</View>
<IconButton
icon="arrow-down-bold"
size={40}
onPressIn={() => {
this.logic.downPressedIn(this.updateGridScore);
}}
onPress={() => {
this.logic.pressedOut();
}}
style={{flex: 1}}
color={props.theme.colors.tetrisScore}
/>
</View>
);
}
updateGrid = (newGrid: GridType) => {
this.setState({
grid: newGrid,
});
};
updateGridScore = (newGrid: GridType, score?: number) => {
this.setState((prevState: StateType): {
grid: GridType;
gameScore: number;
} => ({
grid: newGrid,
gameScore: score != null ? score : prevState.gameScore,
}));
};
togglePause = () => {
this.logic.togglePause();
if (this.logic.isGamePaused()) {
this.showPausePopup();
}
};
showPausePopup = () => {
const onDismiss = () => {
this.togglePause();
this.onDialogDismiss();
};
this.setState({
dialogVisible: true,
dialogTitle: i18n.t('screens.game.pause'),
dialogMessage: i18n.t('screens.game.pauseMessage'),
dialogButtons: [
{
title: i18n.t('screens.game.restart.text'),
onPress: this.showRestartConfirm,
},
{
title: i18n.t('screens.game.resume'),
onPress: onDismiss,
},
],
onDialogDismiss: onDismiss,
});
};
showRestartConfirm = () => {
this.setState({
dialogVisible: true,
dialogTitle: i18n.t('screens.game.restart.confirm'),
dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
dialogButtons: [
{
title: i18n.t('screens.game.restart.confirmYes'),
onPress: () => {
this.onDialogDismiss();
this.startGame();
},
},
{
title: i18n.t('screens.game.restart.confirmNo'),
onPress: this.showPausePopup,
},
],
onDialogDismiss: this.showPausePopup,
});
};
startGame = () => {
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
};
render() {
const {props, state} = this;
return (
<View style={{flex: 1}}>
<View
style={{
flex: 1,
flexDirection: 'row',
}}>
{this.getStatusIcons()}
<View style={{flex: 4}}>
{this.getScoreIcon()}
<GridComponent
width={this.logic.getWidth()}
height={this.logic.getHeight()}
grid={state.grid}
style={{
backgroundColor: props.theme.colors.tetrisBackground,
flex: 1,
marginLeft: 'auto',
marginRight: 'auto',
}}
/>
</View>
<View style={{flex: 1}}>
<Preview
items={this.logic.getNextPiecesPreviews()}
style={{
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 10,
}}
/>
</View>
</View>
{this.getControlButtons()}
<OptionsDialog
visible={state.dialogVisible}
title={state.dialogTitle}
message={state.dialogMessage}
buttons={state.dialogButtons}
onDismiss={state.onDialogDismiss}
/>
</View>
);
}
}
export default withTheme(GameMainScreen);