From b2ff90855f4c974d66eaa2a830842e8a9e722514 Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 21 Jul 2020 20:43:03 +0200 Subject: [PATCH] Added game score save support --- locales/en.json | 8 +- locales/fr.json | 2 + src/components/Mascot/MascotPopup.js | 19 +- src/components/Mascot/SpeechArrow.js | 33 ++ src/managers/AsyncStorageManager.js | 5 + src/managers/ThemeManager.js | 12 + src/screens/Game/screens/GameMainScreen.js | 90 ++++-- src/screens/Game/screens/GameStartScreen.js | 340 ++++++++++++++++++-- 8 files changed, 427 insertions(+), 82 deletions(-) create mode 100644 src/components/Mascot/SpeechArrow.js diff --git a/locales/en.json b/locales/en.json index aada7d0..5b7c8a6 100644 --- a/locales/en.json +++ b/locales/en.json @@ -366,9 +366,11 @@ "welcomeTitle": "Welcome !", "welcomeMessage": "Stuck on the toilet? The teacher is late?\nThis game is for you!\n\nTry to get the best score and beat your friends.", "play": "Play!", - "score": "Score : %{score}", - "time": "Time :", - "level": "Level :", + "score": "Score: %{score}", + "highScore": "High score: %{score}", + "newHighScore": "New High Score!", + "time": "Time:", + "level": "Level:", "pause": "Game Paused", "pauseMessage": "The game is paused", "resume": "Resume", diff --git a/locales/fr.json b/locales/fr.json index a744a7d..33ccc4e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -366,6 +366,8 @@ "welcomeMessage": "Coincé sur les WC ? Le prof est pas là ?\nCe jeu est fait pour toi !\n\nEssaie d'avoir le meilleur score et de battre tes amis.", "play": "Jouer !", "score": "Score : %{score}", + "highScore": "Meilleur score : %{score}", + "newHighScore": "Meilleur score !", "time": "Temps :", "level": "Niveau :", "pause": "Pause", diff --git a/src/components/Mascot/MascotPopup.js b/src/components/Mascot/MascotPopup.js index dc0df7c..47ad5a4 100644 --- a/src/components/Mascot/MascotPopup.js +++ b/src/components/Mascot/MascotPopup.js @@ -6,6 +6,7 @@ import Mascot from "./Mascot"; import * as Animatable from "react-native-animatable"; import {BackHandler, Dimensions, ScrollView, TouchableWithoutFeedback, View} from "react-native"; import type {CustomTheme} from "../../managers/ThemeManager"; +import SpeechArrow from "./SpeechArrow"; type Props = { visible: boolean, @@ -102,19 +103,11 @@ class MascotPopup extends React.Component { animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"} duration={this.props.visible ? 1000 : 300} > - + { + + render() { + return ( + + + + ); + } +} diff --git a/src/managers/AsyncStorageManager.js b/src/managers/AsyncStorageManager.js index 242d839..a2f151a 100644 --- a/src/managers/AsyncStorageManager.js +++ b/src/managers/AsyncStorageManager.js @@ -136,6 +136,11 @@ export default class AsyncStorageManager { ]), current: '', }, + gameScores: { + key: 'gameScores', + default: '[]', + current: '', + }, }; /** diff --git a/src/managers/ThemeManager.js b/src/managers/ThemeManager.js index 7404aeb..c0fb3ae 100644 --- a/src/managers/ThemeManager.js +++ b/src/managers/ThemeManager.js @@ -56,6 +56,10 @@ export type CustomTheme = { tetrisJ: string, tetrisL: string, + gameGold: string, + gameSilver: string, + gameBronze: string, + // Mascot Popup mascotMessageArrow: string, }, @@ -129,6 +133,10 @@ export default class ThemeManager { tetrisJ: '#2a67e3', tetrisL: '#da742d', + gameGold: "#ffd610", + gameSilver: "#7b7b7b", + gameBronze: "#a15218", + // Mascot Popup mascotMessageArrow: "#dedede", }, @@ -191,6 +199,10 @@ export default class ThemeManager { tetrisJ: '#0f37b9', tetrisL: '#b96226', + gameGold: "#ffd610", + gameSilver: "#7b7b7b", + gameBronze: "#a15218", + // Mascot Popup mascotMessageArrow: "#323232", }, diff --git a/src/screens/Game/screens/GameMainScreen.js b/src/screens/Game/screens/GameMainScreen.js index 2d0bd3a..23dd313 100644 --- a/src/screens/Game/screens/GameMainScreen.js +++ b/src/screens/Game/screens/GameMainScreen.js @@ -17,6 +17,7 @@ import OptionsDialog from "../../../components/Dialogs/OptionsDialog"; type Props = { navigation: StackNavigationProp, + route: { params: { highScore: number }, ... }, theme: CustomTheme, } @@ -37,6 +38,7 @@ type State = { class GameMainScreen extends React.Component { logic: GameLogic; + highScore: number | null; constructor(props) { super(props); @@ -54,8 +56,8 @@ class GameMainScreen extends React.Component { onDialogDismiss: () => { }, }; - this.props.navigation.addListener('blur', this.onScreenBlur); - this.props.navigation.addListener('focus', this.onScreenFocus); + if (this.props.route.params != null) + this.highScore = this.props.route.params.highScore; } componentDidMount() { @@ -71,21 +73,6 @@ class GameMainScreen extends React.Component { ; } - /** - * Remove any interval on un-focus - */ - onScreenBlur = () => { - if (!this.logic.isGamePaused()) - this.logic.togglePause(); - } - - onScreenFocus = () => { - if (!this.logic.isGameRunning()) - this.startGame(); - else if (this.logic.isGamePaused()) - this.showPausePopup(); - } - getFormattedTime(seconds: number) { let date = new Date(); date.setHours(0); @@ -221,7 +208,14 @@ class GameMainScreen extends React.Component { gameRunning: false, }); if (!isRestart) - this.showGameOverConfirm(); + this.props.navigation.replace( + "game-start", + { + score: this.state.gameScore, + level: this.state.gameLevel, + time: this.state.gameTime, + } + ); } getStatusIcons() { @@ -281,28 +275,56 @@ class GameMainScreen extends React.Component { } getScoreIcon() { + let highScore = this.highScore == null || this.state.gameScore > this.highScore + ? this.state.gameScore + : this.highScore; return ( - {i18n.t("screens.game.score", {score: this.state.gameScore})} - + + {i18n.t("screens.game.score", {score: this.state.gameScore})} + + + + {i18n.t("screens.game.highScore", {score: highScore})} + + + ); } diff --git a/src/screens/Game/screens/GameStartScreen.js b/src/screens/Game/screens/GameStartScreen.js index f48c822..99b2a7c 100644 --- a/src/screens/Game/screens/GameStartScreen.js +++ b/src/screens/Game/screens/GameStartScreen.js @@ -3,8 +3,8 @@ import * as React from "react"; import {StackNavigationProp} from "@react-navigation/stack"; import type {CustomTheme} from "../../../managers/ThemeManager"; -import {Button, Card, Divider, Headline, Paragraph, withTheme} from "react-native-paper"; -import {View} from "react-native"; +import {Button, Card, Divider, Headline, Paragraph, Text, withTheme} from "react-native-paper"; +import {ScrollView, View} from "react-native"; import i18n from "i18n-js"; import Mascot, {MASCOT_STYLE} from "../../../components/Mascot/Mascot"; import MascotPopup from "../../../components/Mascot/MascotPopup"; @@ -14,9 +14,21 @@ import GridComponent from "../components/GridComponent"; import GridManager from "../logic/GridManager"; import Piece from "../logic/Piece"; import * as Animatable from "react-native-animatable"; +import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; +import LinearGradient from "react-native-linear-gradient"; +import SpeechArrow from "../../../components/Mascot/SpeechArrow"; + +type GameStats = { + score: number, + level: number, + time: number, +} type Props = { navigation: StackNavigationProp, + route: { + params: GameStats + }, theme: CustomTheme, } @@ -27,6 +39,10 @@ type State = { class GameStartScreen extends React.Component { gridManager: GridManager; + scores: Array; + + gameStats: GameStats | null; + isHighScore: boolean; state = { mascotDialogVisible: AsyncStorageManager.getInstance().preferences.gameStartShowBanner.current === "1", @@ -35,6 +51,30 @@ class GameStartScreen extends React.Component { constructor(props: Props) { super(props); this.gridManager = new GridManager(4, 4, props.theme); + this.scores = JSON.parse(AsyncStorageManager.getInstance().preferences.gameScores.current); + this.scores.sort((a, b) => b - a); + if (this.props.route.params != null) + this.recoverGameScore(); + } + + recoverGameScore() { + this.gameStats = this.props.route.params; + this.isHighScore = this.scores.length === 0 || this.gameStats.score > this.scores[0]; + for (let i = 0; i < 3; i++) { + 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); + AsyncStorageManager.getInstance().savePref( + AsyncStorageManager.getInstance().preferences.gameScores.key, + JSON.stringify(this.scores) + ); } hideMascotDialog = () => { @@ -97,10 +137,110 @@ class GameStartScreen extends React.Component { ); })} - ); } + getPostGameContent(stats: GameStats) { + return ( + + + + + + + {this.isHighScore + ? i18n.t("screens.game.newHighScore") + : i18n.t("screens.game.gameOver.text")} + + + + + {i18n.t("screens.game.score", {score: stats.score})} + + + + + {i18n.t("screens.game.level")} + + + {stats.level} + + + + {i18n.t("screens.game.time")} + + + {stats.time} + + + + + + ) + } + getWelcomeText() { return ( @@ -109,7 +249,14 @@ class GameStartScreen extends React.Component { marginLeft: "auto", marginRight: "auto", }}/> + @@ -131,44 +278,173 @@ class GameStartScreen extends React.Component { - ); } + getPodiumRender(place: 1 | 2 | 3, score: string) { + let icon = "podium-gold"; + let color = this.props.theme.colors.gameGold; + let fontSize = 20; + let size = 70; + if (place === 2) { + icon = "podium-silver"; + color = this.props.theme.colors.gameSilver; + fontSize = 18; + size = 60; + } else if (place === 3) { + icon = "podium-bronze"; + color = this.props.theme.colors.gameBronze; + fontSize = 15; + size = 50; + } + 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() { + return ( + + + { + this.gameStats != null + ? this.getPostGameContent(this.gameStats) + : this.getWelcomeText() + } + + {this.getTopScoresRender()} + + + ) + } + + keyExtractor = (item: number) => item.toString(); + render() { return ( {this.getPiecesBackground()} - {this.getWelcomeText()} - - + + {this.getMainContent()} + + + ); } }