From 9ae585bdf8e1103f4455f6c318e282dd7f5438b4 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Sat, 22 May 2021 15:39:35 +0200 Subject: [PATCH] Convert game into functional component --- .../Game/components/FullGamePodium.tsx | 44 ++ .../Game/components/GameBrackground.tsx | 65 +++ src/screens/Game/components/GameControls.tsx | 62 +++ src/screens/Game/components/GamePodium.tsx | 95 ++++ src/screens/Game/components/GameScore.tsx | 81 +++ src/screens/Game/components/GameStatus.tsx | 96 ++++ .../Game/components/PostGameContent.tsx | 130 +++++ .../Game/components/WelcomeGameContent.tsx | 70 +++ src/screens/Game/screens/GameMainScreen.tsx | 478 +++++------------ src/screens/Game/screens/GameStartScreen.tsx | 505 +++--------------- 10 files changed, 856 insertions(+), 770 deletions(-) create mode 100644 src/screens/Game/components/FullGamePodium.tsx create mode 100644 src/screens/Game/components/GameBrackground.tsx create mode 100644 src/screens/Game/components/GameControls.tsx create mode 100644 src/screens/Game/components/GamePodium.tsx create mode 100644 src/screens/Game/components/GameScore.tsx create mode 100644 src/screens/Game/components/GameStatus.tsx create mode 100644 src/screens/Game/components/PostGameContent.tsx create mode 100644 src/screens/Game/components/WelcomeGameContent.tsx diff --git a/src/screens/Game/components/FullGamePodium.tsx b/src/screens/Game/components/FullGamePodium.tsx new file mode 100644 index 0000000..c4e6a58 --- /dev/null +++ b/src/screens/Game/components/FullGamePodium.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { GamePodium } from './GamePodium'; + +type Props = { + scores: Array; + isHighScore: boolean; +}; + +const styles = StyleSheet.create({ + topScoreContainer: { + marginBottom: 20, + marginTop: 20, + }, + topScoreSubcontainer: { + flexDirection: 'row', + marginLeft: 'auto', + marginRight: 'auto', + }, +}); + +export default function FullGamePodium(props: Props) { + const { scores, isHighScore } = props; + const gold = scores.length > 0 ? scores[0] : '-'; + const silver = scores.length > 1 ? scores[1] : '-'; + const bronze = scores.length > 2 ? scores[2] : '-'; + return ( + + + + + + + + ); +} diff --git a/src/screens/Game/components/GameBrackground.tsx b/src/screens/Game/components/GameBrackground.tsx new file mode 100644 index 0000000..0f364e2 --- /dev/null +++ b/src/screens/Game/components/GameBrackground.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import * as Animatable from 'react-native-animatable'; +import { useTheme } from 'react-native-paper'; +import GridManager from '../logic/GridManager'; +import Piece from '../logic/Piece'; +import GridComponent from './GridComponent'; + +const styles = StyleSheet.create({ + pieceContainer: { + position: 'absolute', + width: '100%', + height: '100%', + }, + pieceBackground: { + position: 'absolute', + }, +}); + +export default function GameBackground() { + const theme = useTheme(); + const gridManager = new GridManager(4, 4, theme); + const gridList = []; + for (let i = 0; i < 18; i += 1) { + gridList.push(gridManager.getEmptyGrid(4, 4)); + const piece = new Piece(theme); + piece.toGrid(gridList[i], true); + } + return ( + + {gridList.map((item, index) => { + const size = 10 + Math.floor(Math.random() * 30); + const top = Math.floor(Math.random() * 100); + const rot = Math.floor(Math.random() * 360); + const left = (index % 6) * 20; + const animDelay = size * 20; + const animDuration = 2 * (2000 - size * 30); + return ( + + + + ); + })} + + ); +} diff --git a/src/screens/Game/components/GameControls.tsx b/src/screens/Game/components/GameControls.tsx new file mode 100644 index 0000000..81eb774 --- /dev/null +++ b/src/screens/Game/components/GameControls.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { IconButton, useTheme } from 'react-native-paper'; +import GENERAL_STYLES from '../../../constants/Styles'; +import GameLogic, { MovementCallbackType } from '../logic/GameLogic'; + +type Props = { + logic: GameLogic; + onDirectionPressed: MovementCallbackType; +}; + +const styles = StyleSheet.create({ + controlsContainer: { + height: 80, + flexDirection: 'row', + }, + directionsContainer: { + flexDirection: 'row', + flex: 4, + }, +}); + +function GameControls(props: Props) { + const { logic } = props; + const theme = useTheme(); + return ( + + logic.rotatePressed(props.onDirectionPressed)} + style={GENERAL_STYLES.flex} + /> + + logic.pressedOut()} + onPressIn={() => logic.leftPressedIn(props.onDirectionPressed)} + /> + logic.pressedOut()} + onPressIn={() => logic.rightPressed(props.onDirectionPressed)} + /> + + logic.downPressedIn(props.onDirectionPressed)} + onPress={() => logic.pressedOut()} + style={GENERAL_STYLES.flex} + color={theme.colors.tetrisScore} + /> + + ); +} + +export default React.memo(GameControls, () => true); diff --git a/src/screens/Game/components/GamePodium.tsx b/src/screens/Game/components/GamePodium.tsx new file mode 100644 index 0000000..51a54a6 --- /dev/null +++ b/src/screens/Game/components/GamePodium.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Text, useTheme } from 'react-native-paper'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import * as Animatable from 'react-native-animatable'; + +type Props = { + place: 1 | 2 | 3; + score: string; + isHighScore: boolean; +}; + +const styles = StyleSheet.create({ + podiumContainer: { + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'flex-end', + }, + podiumIconContainer: { + position: 'absolute', + top: -20, + }, + centertext: { + textAlign: 'center', + }, +}); + +export function GamePodium(props: Props) { + const { place, score, isHighScore } = props; + const theme = useTheme(); + let icon = 'podium-gold'; + let color = theme.colors.gameGold; + let fontSize = 20; + let size = 70; + if (place === 2) { + icon = 'podium-silver'; + color = theme.colors.gameSilver; + fontSize = 18; + size = 60; + } else if (place === 3) { + icon = 'podium-bronze'; + color = theme.colors.gameBronze; + fontSize = 15; + size = 50; + } + const marginLeft = place === 2 ? 20 : 'auto'; + const marginRight = place === 3 ? 20 : 'auto'; + const fontWeight = place === 1 ? 'bold' : undefined; + return ( + + {isHighScore && place === 1 ? ( + + + + + + ) : null} + + + {score} + + + ); +} diff --git a/src/screens/Game/components/GameScore.tsx b/src/screens/Game/components/GameScore.tsx new file mode 100644 index 0000000..a3a2173 --- /dev/null +++ b/src/screens/Game/components/GameScore.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { View } from 'react-native'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import i18n from 'i18n-js'; +import { Text, useTheme } from 'react-native-paper'; +import { StyleSheet } from 'react-native'; +import GENERAL_STYLES from '../../../constants/Styles'; + +type Props = { + score: number; + highScore?: number; +}; + +const styles = StyleSheet.create({ + scoreMainContainer: { + marginTop: 10, + marginBottom: 10, + }, + scoreCurrentContainer: { + flexDirection: 'row', + marginLeft: 'auto', + marginRight: 'auto', + }, + scoreText: { + marginLeft: 5, + fontSize: 20, + }, + scoreBestContainer: { + flexDirection: 'row', + marginLeft: 'auto', + marginRight: 'auto', + marginTop: 5, + }, + centerVerticalSmallMargin: { + ...GENERAL_STYLES.centerVertical, + marginLeft: 5, + }, +}); + +function GameScore(props: Props) { + const { score, highScore } = props; + const theme = useTheme(); + const displayHighScore = + highScore == null || score > highScore ? score : highScore; + return ( + + + + {i18n.t('screens.game.score', { score: score })} + + + + + + {i18n.t('screens.game.highScore', { score: displayHighScore })} + + + + + ); +} + +export default React.memo( + GameScore, + (pp, np) => pp.highScore === np.highScore && pp.score === np.score +); diff --git a/src/screens/Game/components/GameStatus.tsx b/src/screens/Game/components/GameStatus.tsx new file mode 100644 index 0000000..ce9c4b2 --- /dev/null +++ b/src/screens/Game/components/GameStatus.tsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Caption, Text, useTheme } from 'react-native-paper'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import GENERAL_STYLES from '../../../constants/Styles'; +import i18n from 'i18n-js'; + +type Props = { + time: number; + level: number; +}; + +const styles = StyleSheet.create({ + centerSmallMargin: { + ...GENERAL_STYLES.centerHorizontal, + marginBottom: 5, + }, + centerBigMargin: { + marginLeft: 'auto', + marginRight: 'auto', + marginBottom: 20, + }, + statusContainer: { + flexDirection: 'row', + }, + statusIcon: { + marginLeft: 5, + }, +}); + +function 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; +} + +function GameStatus(props: Props) { + const theme = useTheme(); + return ( + + + + {i18n.t('screens.game.time')} + + + + + {getFormattedTime(props.time)} + + + + + + {i18n.t('screens.game.level')} + + + + {props.level} + + + + ); +} + +export default React.memo( + GameStatus, + (pp, np) => pp.level === np.level && pp.time === np.time +); diff --git a/src/screens/Game/components/PostGameContent.tsx b/src/screens/Game/components/PostGameContent.tsx new file mode 100644 index 0000000..fe24636 --- /dev/null +++ b/src/screens/Game/components/PostGameContent.tsx @@ -0,0 +1,130 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { Card, Divider, Headline, Text, useTheme } from 'react-native-paper'; +import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot'; +import SpeechArrow from '../../../components/Mascot/SpeechArrow'; +import GENERAL_STYLES from '../../../constants/Styles'; +import i18n from 'i18n-js'; + +type GameStatsType = { + score: number; + level: number; + time: number; +}; + +type Props = { + isHighScore: boolean; + stats: GameStatsType; +}; + +const styles = StyleSheet.create({ + recapCard: { + borderWidth: 2, + marginLeft: 20, + marginRight: 20, + }, + recapContainer: { + flexDirection: 'row', + marginLeft: 'auto', + marginRight: 'auto', + }, + recapScoreContainer: { + flexDirection: 'row', + marginLeft: 'auto', + marginRight: 'auto', + marginTop: 10, + marginBottom: 10, + }, + recapScore: { + fontSize: 20, + }, + recapScoreIcon: { + marginLeft: 5, + }, + recapIcon: { + marginRight: 5, + marginLeft: 5, + }, + centertext: { + textAlign: 'center', + }, +}); + +export default function PostGameContent(props: Props) { + const { isHighScore, stats } = props; + const theme = useTheme(); + const width = isHighScore ? '50%' : '30%'; + const margin = isHighScore ? 'auto' : undefined; + const marginLeft = isHighScore ? '60%' : '20%'; + const color = isHighScore ? theme.colors.gameGold : theme.colors.primary; + return ( + + + + + + + {isHighScore + ? i18n.t('screens.game.newHighScore') + : i18n.t('screens.game.gameOver')} + + + + + {i18n.t('screens.game.score', { score: stats.score })} + + + + + {i18n.t('screens.game.level')} + + {stats.level} + + + {i18n.t('screens.game.time')} + + {stats.time} + + + + + ); +} diff --git a/src/screens/Game/components/WelcomeGameContent.tsx b/src/screens/Game/components/WelcomeGameContent.tsx new file mode 100644 index 0000000..5cf0293 --- /dev/null +++ b/src/screens/Game/components/WelcomeGameContent.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { + Card, + Divider, + Headline, + Paragraph, + useTheme, +} from 'react-native-paper'; +import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot'; +import SpeechArrow from '../../../components/Mascot/SpeechArrow'; +import i18n from 'i18n-js'; + +const styles = StyleSheet.create({ + welcomeMascot: { + width: '40%', + marginLeft: 'auto', + marginRight: 'auto', + }, + welcomeCard: { + borderWidth: 2, + marginLeft: 10, + marginRight: 10, + }, + speechArrow: { + marginLeft: '60%', + }, + welcomeText: { + textAlign: 'center', + marginTop: 10, + }, + centertext: { + textAlign: 'center', + }, +}); + +export default function WelcomeGameContent() { + const theme = useTheme(); + return ( + + + + + + + {i18n.t('screens.game.welcomeTitle')} + + + + {i18n.t('screens.game.welcomeMessage')} + + + + + ); +} diff --git a/src/screens/Game/screens/GameMainScreen.tsx b/src/screens/Game/screens/GameMainScreen.tsx index 93b51d0..690ab14 100644 --- a/src/screens/Game/screens/GameMainScreen.tsx +++ b/src/screens/Game/screens/GameMainScreen.tsx @@ -17,10 +17,9 @@ * along with Campus INSAT. If not, see . */ -import * as React from 'react'; +import React, { useLayoutEffect, useRef, useState } from 'react'; import { StyleSheet, View } from 'react-native'; -import { Caption, IconButton, Text, withTheme } from 'react-native-paper'; -import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; +import { useTheme } from 'react-native-paper'; import i18n from 'i18n-js'; import { StackNavigationProp } from '@react-navigation/stack'; import GameLogic from '../logic/GameLogic'; @@ -34,25 +33,15 @@ import type { OptionsDialogButtonType } from '../../../components/Dialogs/Option import OptionsDialog from '../../../components/Dialogs/OptionsDialog'; import GENERAL_STYLES from '../../../constants/Styles'; import { MainRoutes } from '../../../navigation/MainNavigator'; - -type PropsType = { - navigation: StackNavigationProp; - 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; - onDialogDismiss: () => void; -}; +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 { useNavigation } from '@react-navigation/core'; const styles = StyleSheet.create({ container: { @@ -62,44 +51,6 @@ const styles = StyleSheet.create({ gridContainer: { flex: 4, }, - centerSmallMargin: { - ...GENERAL_STYLES.centerHorizontal, - marginBottom: 5, - }, - centerVerticalSmallMargin: { - ...GENERAL_STYLES.centerVertical, - marginLeft: 5, - }, - centerBigMargin: { - marginLeft: 'auto', - marginRight: 'auto', - marginBottom: 20, - }, - statusContainer: { - flexDirection: 'row', - }, - statusIcon: { - marginLeft: 5, - }, - scoreMainContainer: { - marginTop: 10, - marginBottom: 10, - }, - scoreCurrentContainer: { - flexDirection: 'row', - marginLeft: 'auto', - marginRight: 'auto', - }, - scoreText: { - marginLeft: 5, - fontSize: 20, - }, - scoreBestContainer: { - flexDirection: 'row', - marginLeft: 'auto', - marginRight: 'auto', - marginTop: 5, - }, controlsContainer: { height: 80, flexDirection: 'row', @@ -115,273 +66,125 @@ const styles = StyleSheet.create({ }, }); -class GameMainScreen extends React.Component { - 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()}`; +export default function GameMainScreen() { + const theme = useTheme(); + const navigation = useNavigation>(); + 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; + onDialogDismiss: () => void; + }>(); + + const { preferences, updatePreferences } = usePreferences(); + + function getScores() { + const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as + | Array + | undefined; + if (pref) { + return pref.sort((a, b) => b - a); } 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; + return []; } } - componentDidMount() { - const { navigation } = this.props; + const savedScores = getScores(); + const highScore = savedScores.length > 0 ? savedScores[0] : undefined; + + useLayoutEffect(() => { navigation.setOptions({ - headerRight: this.getRightButton, + headerRight: getRightButton, }); - this.startGame(); - } + startGame(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [navigation]); - componentWillUnmount() { - this.logic.endGame(true); - } + const getRightButton = () => ( + + + + ); - getRightButton = () => { - return ( - - - - ); - }; - - onTick = (score: number, level: number, newGrid: GridType) => { - this.setState({ + const onTick = (score: number, level: number, newGrid: GridType) => { + setGameState({ gameScore: score, gameLevel: level, grid: newGrid, }); }; - onClock = (time: number) => { - this.setState({ - gameTime: time, - }); - }; + const onDialogDismiss = () => setDialogContent(undefined); - onDialogDismiss = () => { - this.setState({ dialogVisible: false }); - }; - - onGameEnd = (time: number, score: number, isRestart: boolean) => { - const { props, state } = this; - this.setState({ - gameTime: time, + 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); + } + console.log(newScores); + updatePreferences(PreferenceKeys.gameScores, newScores); if (!isRestart) { - props.navigation.replace(MainRoutes.GameStart, { - score: state.gameScore, - level: state.gameLevel, - time: state.gameTime, + navigation.replace(MainRoutes.GameStart, { + score: score, + level: gameState.gameLevel, + time: time, + isHighScore: isHighScore, }); } }; - getStatusIcons() { - const { props, state } = this; - return ( - - - - {i18n.t('screens.game.time')} - - - - - {GameMainScreen.getFormattedTime(state.gameTime)} - - - - - - {i18n.t('screens.game.level')} - - - - {state.gameLevel} - - - - ); - } - - getScoreIcon() { - const { props, state } = this; - const highScore = - this.highScore == null || state.gameScore > this.highScore - ? state.gameScore - : this.highScore; - return ( - - - - {i18n.t('screens.game.score', { score: state.gameScore })} - - - - - - {i18n.t('screens.game.highScore', { score: highScore })} - - - - - ); - } - - getControlButtons() { - const { props } = this; - return ( - - { - this.logic.rotatePressed(this.updateGrid); - }} - style={GENERAL_STYLES.flex} - /> - - { - this.logic.pressedOut(); - }} - onPressIn={() => { - this.logic.leftPressedIn(this.updateGrid); - }} - /> - { - this.logic.pressedOut(); - }} - onPressIn={() => { - this.logic.rightPressed(this.updateGrid); - }} - /> - - { - this.logic.downPressedIn(this.updateGridScore); - }} - onPress={() => { - this.logic.pressedOut(); - }} - style={GENERAL_STYLES.flex} - color={props.theme.colors.tetrisScore} - /> - - ); - } - - updateGrid = (newGrid: GridType) => { - this.setState({ - grid: newGrid, - }); - }; - - updateGridScore = (newGrid: GridType, score?: number) => { - this.setState((prevState: StateType): { - grid: GridType; - gameScore: number; - } => ({ + const onDirectionPressed = (newGrid: GridType, score?: number) => { + setGameState((prevState) => ({ + ...prevState, grid: newGrid, gameScore: score != null ? score : prevState.gameScore, })); }; - togglePause = () => { - this.logic.togglePause(); - if (this.logic.isGamePaused()) { - this.showPausePopup(); + const togglePause = () => { + logic.current.togglePause(); + if (logic.current.isGamePaused()) { + showPausePopup(); } }; - showPausePopup = () => { + const showPausePopup = () => { const onDismiss = () => { - this.togglePause(); - this.onDialogDismiss(); + togglePause(); + onDialogDismiss(); }; - this.setState({ - dialogVisible: true, + setDialogContent({ dialogTitle: i18n.t('screens.game.pause'), dialogMessage: i18n.t('screens.game.pauseMessage'), dialogButtons: [ { title: i18n.t('screens.game.restart.text'), - onPress: this.showRestartConfirm, + onPress: showRestartConfirm, }, { title: i18n.t('screens.game.resume'), @@ -392,71 +195,68 @@ class GameMainScreen extends React.Component { }); }; - showRestartConfirm = () => { - this.setState({ - dialogVisible: true, + 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: () => { - this.onDialogDismiss(); - this.startGame(); + onDialogDismiss(); + startGame(); }, }, { title: i18n.t('screens.game.restart.confirmNo'), - onPress: this.showPausePopup, + onPress: showPausePopup, }, ], - onDialogDismiss: this.showPausePopup, + onDialogDismiss: showPausePopup, }); }; - startGame = () => { - this.logic.startGame(this.onTick, this.onClock, this.onGameEnd); + const startGame = () => { + logic.current.startGame(onTick, setGameTime, onGameEnd); }; - render() { - const { props, state } = this; - return ( - - - {this.getStatusIcons()} - - {this.getScoreIcon()} - - - - - - + return ( + + + + + + + + + - {this.getControlButtons()} - - - ); - } + + {dialogContent ? ( + + ) : null} + + ); } - -export default withTheme(GameMainScreen); diff --git a/src/screens/Game/screens/GameStartScreen.tsx b/src/screens/Game/screens/GameStartScreen.tsx index 0067d37..c72cb3d 100644 --- a/src/screens/Game/screens/GameStartScreen.tsx +++ b/src/screens/Game/screens/GameStartScreen.tsx @@ -18,478 +18,121 @@ */ import * as React from 'react'; -import { StackNavigationProp } from '@react-navigation/stack'; -import { - Button, - Card, - Divider, - Headline, - Paragraph, - Text, - withTheme, -} from 'react-native-paper'; +import { Button, useTheme } from 'react-native-paper'; import { StyleSheet, View } from 'react-native'; import i18n from 'i18n-js'; -import * as Animatable from 'react-native-animatable'; -import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import LinearGradient from 'react-native-linear-gradient'; -import Mascot, { MASCOT_STYLE } from '../../../components/Mascot/Mascot'; +import { MASCOT_STYLE } from '../../../components/Mascot/Mascot'; import MascotPopup from '../../../components/Mascot/MascotPopup'; -import type { GridType } from '../components/GridComponent'; -import GridComponent from '../components/GridComponent'; -import GridManager from '../logic/GridManager'; -import Piece from '../logic/Piece'; -import SpeechArrow from '../../../components/Mascot/SpeechArrow'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; import GENERAL_STYLES from '../../../constants/Styles'; +import GameBackground from '../components/GameBrackground'; +import PostGameContent from '../components/PostGameContent'; +import WelcomeGameContent from '../components/WelcomeGameContent'; +import FullGamePodium from '../components/FullGamePodium'; +import { useNavigation } from '@react-navigation/core'; +import { usePreferences } from '../../../context/preferencesContext'; +import { + getPreferenceObject, + PreferenceKeys, +} from '../../../utils/asyncStorage'; +import { StackNavigationProp } from '@react-navigation/stack'; type GameStatsType = { score: number; level: number; time: number; + isHighScore: boolean; }; -type PropsType = { - navigation: StackNavigationProp; +type Props = { route: { - params: GameStatsType; + params?: GameStatsType; }; - theme: ReactNativePaper.Theme; }; const styles = StyleSheet.create({ - pieceContainer: { - position: 'absolute', - width: '100%', - height: '100%', - }, - pieceBackground: { - position: 'absolute', - }, playButton: { marginLeft: 'auto', marginRight: 'auto', marginTop: 10, }, - recapCard: { - borderWidth: 2, - marginLeft: 20, - marginRight: 20, - }, - recapContainer: { - flexDirection: 'row', - marginLeft: 'auto', - marginRight: 'auto', - }, - recapScoreContainer: { - flexDirection: 'row', - marginLeft: 'auto', - marginRight: 'auto', - marginTop: 10, - marginBottom: 10, - }, - recapScore: { - fontSize: 20, - }, - recapScoreIcon: { - marginLeft: 5, - }, - recapIcon: { - marginRight: 5, - marginLeft: 5, - }, - welcomeMascot: { - width: '40%', - marginLeft: 'auto', - marginRight: 'auto', - }, - welcomeCard: { - borderWidth: 2, - marginLeft: 10, - marginRight: 10, - }, - centertext: { - textAlign: 'center', - }, - welcomeText: { - textAlign: 'center', - marginTop: 10, - }, - speechArrow: { - marginLeft: '60%', - }, - podiumContainer: { - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'flex-end', - }, - podiumIconContainer: { - position: 'absolute', - top: -20, - }, - topScoreContainer: { - marginBottom: 20, - marginTop: 20, - }, - topScoreSubcontainer: { - flexDirection: 'row', - marginLeft: 'auto', - marginRight: 'auto', - }, }); -class GameStartScreen extends React.Component { - gridManager: GridManager; +export default function GameStartScreen(props: Props) { + const theme = useTheme(); + const navigation = useNavigation>(); - scores: Array; + const { preferences } = usePreferences(); - gameStats?: GameStatsType; - - isHighScore: boolean; - - constructor(props: PropsType) { - super(props); - this.isHighScore = false; - this.gridManager = new GridManager(4, 4, props.theme); - // TODO - // this.scores = AsyncStorageManager.getObject( - // AsyncStorageManager.PREFERENCES.gameScores.key - // ); - this.scores = []; - this.scores.sort((a: number, b: number): number => b - a); - if (props.route.params != null) { - this.recoverGameScore(); + function getScores() { + const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as + | Array + | undefined; + if (pref) { + return pref.sort((a, b) => b - a); + } else { + return []; } } - getPiecesBackground() { - const { theme } = this.props; - const gridList = []; - for (let i = 0; i < 18; i += 1) { - gridList.push(this.gridManager.getEmptyGrid(4, 4)); - const piece = new Piece(theme); - piece.toGrid(gridList[i], true); - } - return ( - - {gridList.map((item: GridType, index: number) => { - const size = 10 + Math.floor(Math.random() * 30); - const top = Math.floor(Math.random() * 100); - const rot = Math.floor(Math.random() * 360); - const left = (index % 6) * 20; - const animDelay = size * 20; - const animDuration = 2 * (2000 - size * 30); - return ( - - - - ); - })} - - ); - } + const scores = getScores(); + const lastGameStats = props.route.params; - getPostGameContent(stats: GameStatsType) { - const { props } = this; - const width = this.isHighScore ? '50%' : '30%'; - const margin = this.isHighScore ? 'auto' : undefined; - const marginLeft = this.isHighScore ? '60%' : '20%'; - const color = this.isHighScore - ? props.theme.colors.gameGold - : props.theme.colors.primary; + const getMainContent = () => { return ( - - - - - - {this.isHighScore - ? i18n.t('screens.game.newHighScore') - : i18n.t('screens.game.gameOver')} - - - - - {i18n.t('screens.game.score', { score: stats.score })} - - - - - {i18n.t('screens.game.level')} - - {stats.level} - - - {i18n.t('screens.game.time')} - - {stats.time} - - - - - ); - } - - getWelcomeText() { - const { props } = this; - return ( - - - - - - - {i18n.t('screens.game.welcomeTitle')} - - - - {i18n.t('screens.game.welcomeMessage')} - - - - - ); - } - - getPodiumRender(place: 1 | 2 | 3, score: string) { - const { props } = this; - let icon = 'podium-gold'; - let color = props.theme.colors.gameGold; - let fontSize = 20; - let size = 70; - if (place === 2) { - icon = 'podium-silver'; - color = props.theme.colors.gameSilver; - fontSize = 18; - size = 60; - } else if (place === 3) { - icon = 'podium-bronze'; - color = props.theme.colors.gameBronze; - fontSize = 15; - size = 50; - } - const marginLeft = place === 2 ? 20 : 'auto'; - const marginRight = place === 3 ? 20 : 'auto'; - const fontWeight = place === 1 ? 'bold' : undefined; - return ( - - {this.isHighScore && place === 1 ? ( - - - - - - ) : null} - - - {score} - - - ); - } - - getTopScoresRender() { - const gold = this.scores.length > 0 ? this.scores[0] : '-'; - const silver = this.scores.length > 1 ? this.scores[1] : '-'; - const bronze = this.scores.length > 2 ? this.scores[2] : '-'; - return ( - - {this.getPodiumRender(1, gold.toString())} - - {this.getPodiumRender(3, bronze.toString())} - {this.getPodiumRender(2, silver.toString())} - - - ); - } - - getMainContent() { - const { props } = this; - return ( - - {this.gameStats != null - ? this.getPostGameContent(this.gameStats) - : this.getWelcomeText()} + {lastGameStats ? ( + + ) : ( + + )} - {this.getTopScoresRender()} + ); - } + }; - keyExtractor = (item: number): string => item.toString(); - - recoverGameScore() { - const { route } = this.props; - this.gameStats = route.params; - if (this.gameStats.score != null) { - this.isHighScore = - this.scores.length === 0 || this.gameStats.score > this.scores[0]; - for (let i = 0; i < 3; i += 1) { - if (this.scores.length > i && this.gameStats.score > this.scores[i]) { - this.scores.splice(i, 0, this.gameStats.score); - break; - } else if (this.scores.length <= i) { - this.scores.push(this.gameStats.score); - break; - } - } - if (this.scores.length > 3) { - this.scores.splice(3, 1); - } - // TODO - // AsyncStorageManager.set( - // AsyncStorageManager.PREFERENCES.gameScores.key, - // this.scores - // ); - } - } - - render() { - const { props } = this; - return ( - - {this.getPiecesBackground()} - - - {this.getMainContent()} - - - - - ); - } + return ( + + + + + {getMainContent()} + + + + + ); } - -export default withTheme(GameStartScreen);