123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- /*
- * 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 React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
- import { StyleSheet, View } from 'react-native';
- import { useTheme } from 'react-native-paper';
- 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';
- import GENERAL_STYLES from '../../../constants/Styles';
- import { MainRoutes } from '../../../navigation/MainNavigator';
- import GameStatus from '../components/GameStatus';
- import GameControls from '../components/GameControls';
- import GameScore from '../components/GameScore';
- import { usePreferences } from '../../../context/preferencesContext';
- import {
- getPreferenceObject,
- PreferenceKeys,
- } from '../../../utils/asyncStorage';
- import { useFocusEffect, useNavigation } from '@react-navigation/core';
-
- const styles = StyleSheet.create({
- container: {
- flex: 1,
- flexDirection: 'row',
- },
- gridContainer: {
- flex: 4,
- },
- controlsContainer: {
- height: 80,
- flexDirection: 'row',
- },
- directionsContainer: {
- flexDirection: 'row',
- flex: 4,
- },
- preview: {
- marginLeft: 'auto',
- marginRight: 'auto',
- marginTop: 10,
- },
- });
-
- export default function GameMainScreen() {
- const theme = useTheme();
- const navigation = useNavigation<StackNavigationProp<any>>();
- const logic = useRef(new GameLogic(20, 10, theme));
-
- const [gameTime, setGameTime] = useState(0);
-
- const [gameState, setGameState] = useState({
- grid: logic.current.getCurrentGrid(),
- gameScore: 0,
- gameLevel: 0,
- });
-
- const [dialogContent, setDialogContent] =
- useState<{
- dialogTitle: string;
- dialogMessage: string;
- dialogButtons: Array<OptionsDialogButtonType>;
- onDialogDismiss: () => void;
- }>();
-
- const { preferences, updatePreferences } = usePreferences();
-
- function getScores() {
- const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as
- | Array<number>
- | undefined;
- if (pref) {
- return pref.sort((a, b) => b - a);
- } else {
- return [];
- }
- }
-
- const savedScores = getScores();
- const highScore = savedScores.length > 0 ? savedScores[0] : undefined;
-
- useLayoutEffect(() => {
- navigation.setOptions({
- headerRight: getRightButton,
- });
- startGame();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [navigation]);
-
- useFocusEffect(
- useCallback(() => {
- const l = logic.current;
- return () => l.endGame(true);
- }, [])
- );
-
- const getRightButton = () => (
- <MaterialHeaderButtons>
- <Item title={'pause'} iconName={'pause'} onPress={togglePause} />
- </MaterialHeaderButtons>
- );
-
- const onTick = (score: number, level: number, newGrid: GridType) => {
- setGameState({
- gameScore: score,
- gameLevel: level,
- grid: newGrid,
- });
- };
-
- const onDialogDismiss = () => setDialogContent(undefined);
-
- const onGameEnd = (time: number, score: number, isRestart: boolean) => {
- setGameState((prevState) => ({
- ...prevState,
- gameScore: score,
- }));
- setGameTime(time);
- const newScores = [...savedScores];
- const isHighScore = newScores.length === 0 || score > newScores[0];
- for (let i = 0; i < 3; i += 1) {
- if (newScores.length > i && score > newScores[i]) {
- newScores.splice(i, 0, score);
- break;
- } else if (newScores.length <= i) {
- newScores.push(score);
- break;
- }
- }
- if (newScores.length > 3) {
- newScores.splice(3, 1);
- }
- if (newScores.some((item, i) => item !== savedScores[i])) {
- updatePreferences(PreferenceKeys.gameScores, newScores);
- }
- if (!isRestart) {
- navigation.replace(MainRoutes.GameStart, {
- score: score,
- level: gameState.gameLevel,
- time: time,
- isHighScore: isHighScore,
- });
- }
- };
-
- const onDirectionPressed = (newGrid: GridType, score?: number) => {
- setGameState((prevState) => ({
- ...prevState,
- grid: newGrid,
- gameScore: score != null ? score : prevState.gameScore,
- }));
- };
-
- const togglePause = () => {
- logic.current.togglePause();
- if (logic.current.isGamePaused()) {
- showPausePopup();
- }
- };
-
- const showPausePopup = () => {
- const onDismiss = () => {
- togglePause();
- onDialogDismiss();
- };
- setDialogContent({
- dialogTitle: i18n.t('screens.game.pause'),
- dialogMessage: i18n.t('screens.game.pauseMessage'),
- dialogButtons: [
- {
- title: i18n.t('screens.game.restart.text'),
- onPress: showRestartConfirm,
- },
- {
- title: i18n.t('screens.game.resume'),
- onPress: onDismiss,
- },
- ],
- onDialogDismiss: onDismiss,
- });
- };
-
- const showRestartConfirm = () => {
- setDialogContent({
- dialogTitle: i18n.t('screens.game.restart.confirm'),
- dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
- dialogButtons: [
- {
- title: i18n.t('screens.game.restart.confirmYes'),
- onPress: () => {
- onDialogDismiss();
- startGame();
- },
- },
- {
- title: i18n.t('screens.game.restart.confirmNo'),
- onPress: showPausePopup,
- },
- ],
- onDialogDismiss: showPausePopup,
- });
- };
-
- const startGame = () => {
- logic.current.startGame(onTick, setGameTime, onGameEnd);
- };
-
- return (
- <View style={GENERAL_STYLES.flex}>
- <View style={styles.container}>
- <GameStatus time={gameTime} level={gameState.gameLevel} />
- <View style={styles.gridContainer}>
- <GameScore score={gameState.gameScore} highScore={highScore} />
- <GridComponent
- width={logic.current.getWidth()}
- height={logic.current.getHeight()}
- grid={gameState.grid}
- style={{
- backgroundColor: theme.colors.tetrisBackground,
- ...GENERAL_STYLES.flex,
- ...GENERAL_STYLES.centerHorizontal,
- }}
- />
- </View>
- <View style={GENERAL_STYLES.flex}>
- <Preview
- items={logic.current.getNextPiecesPreviews()}
- style={styles.preview}
- />
- </View>
- </View>
- <GameControls
- logic={logic.current}
- onDirectionPressed={onDirectionPressed}
- />
- {dialogContent ? (
- <OptionsDialog
- visible={dialogContent !== undefined}
- title={dialogContent.dialogTitle}
- message={dialogContent.dialogMessage}
- buttons={dialogContent.dialogButtons}
- onDismiss={dialogContent.onDialogDismiss}
- />
- ) : null}
- </View>
- );
- }
|