forked from vergnet/application-amicale
Added game score save support
This commit is contained in:
parent
4f911ce32d
commit
b2ff90855f
8 changed files with 427 additions and 82 deletions
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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<Props, State> {
|
|||
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
|
||||
duration={this.props.visible ? 1000 : 300}
|
||||
>
|
||||
<View style={{
|
||||
marginLeft: this.mascotSize / 3,
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderRightWidth: 20,
|
||||
borderBottomWidth: 20,
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'transparent',
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderBottomColor: this.props.theme.colors.mascotMessageArrow,
|
||||
}}/>
|
||||
<SpeechArrow
|
||||
style={{marginLeft: this.mascotSize / 3}}
|
||||
size={20}
|
||||
color={this.props.theme.colors.mascotMessageArrow}
|
||||
/>
|
||||
<Card style={{
|
||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
||||
borderWidth: 4,
|
||||
|
|
33
src/components/Mascot/SpeechArrow.js
Normal file
33
src/components/Mascot/SpeechArrow.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from "react-native";
|
||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
||||
|
||||
type Props = {
|
||||
style?: ViewStyle,
|
||||
size: number,
|
||||
color: string,
|
||||
}
|
||||
|
||||
export default class SpeechArrow extends React.Component<Props> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={this.props.style}>
|
||||
<View style={{
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeftWidth: 0,
|
||||
borderRightWidth: this.props.size,
|
||||
borderBottomWidth: this.props.size,
|
||||
borderStyle: 'solid',
|
||||
backgroundColor: 'transparent',
|
||||
borderLeftColor: 'transparent',
|
||||
borderRightColor: 'transparent',
|
||||
borderBottomColor: this.props.color,
|
||||
}}/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -136,6 +136,11 @@ export default class AsyncStorageManager {
|
|||
]),
|
||||
current: '',
|
||||
},
|
||||
gameScores: {
|
||||
key: 'gameScores',
|
||||
default: '[]',
|
||||
current: '',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
|
|
|
@ -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<Props, State> {
|
||||
|
||||
logic: GameLogic;
|
||||
highScore: number | null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -54,8 +56,8 @@ class GameMainScreen extends React.Component<Props, State> {
|
|||
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<Props, State> {
|
|||
</MaterialHeaderButtons>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<Props, State> {
|
|||
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,17 +275,22 @@ class GameMainScreen extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
getScoreIcon() {
|
||||
let highScore = this.highScore == null || this.state.gameScore > this.highScore
|
||||
? this.state.gameScore
|
||||
: this.highScore;
|
||||
return (
|
||||
<View style={{
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
fontSize: 22,
|
||||
fontSize: 20,
|
||||
}}>{i18n.t("screens.game.score", {score: this.state.gameScore})}</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={'star'}
|
||||
|
@ -303,6 +302,29 @@ class GameMainScreen extends React.Component<Props, State> {
|
|||
marginLeft: 5
|
||||
}}/>
|
||||
</View>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: 5,
|
||||
}}>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
fontSize: 10,
|
||||
color: this.props.theme.colors.textDisabled
|
||||
}}>{i18n.t("screens.game.highScore", {score: highScore})}</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={'star'}
|
||||
color={this.props.theme.colors.tetrisScore}
|
||||
size={10}
|
||||
style={{
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
marginLeft: 5
|
||||
}}/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Props, State> {
|
||||
|
||||
gridManager: GridManager;
|
||||
scores: Array<number>;
|
||||
|
||||
gameStats: GameStats | null;
|
||||
isHighScore: boolean;
|
||||
|
||||
state = {
|
||||
mascotDialogVisible: AsyncStorageManager.getInstance().preferences.gameStartShowBanner.current === "1",
|
||||
|
@ -35,6 +51,30 @@ class GameStartScreen extends React.Component<Props, State> {
|
|||
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<Props, State> {
|
|||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
getPostGameContent(stats: GameStats) {
|
||||
return (
|
||||
<View style={{
|
||||
flex: 1
|
||||
}}>
|
||||
<Mascot
|
||||
emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
|
||||
animated={this.isHighScore}
|
||||
style={{
|
||||
width: this.isHighScore ? "50%" : "30%",
|
||||
marginLeft: this.isHighScore ? "auto" : null,
|
||||
marginRight: this.isHighScore ? "auto" : null,
|
||||
}}/>
|
||||
<SpeechArrow
|
||||
style={{marginLeft: this.isHighScore ? "60%" : "20%"}}
|
||||
size={20}
|
||||
color={this.props.theme.colors.mascotMessageArrow}
|
||||
/>
|
||||
<Card style={{
|
||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
||||
borderWidth: 2,
|
||||
marginLeft: 20,
|
||||
marginRight: 20,
|
||||
}}>
|
||||
<Card.Content>
|
||||
<Headline
|
||||
style={{
|
||||
textAlign: "center",
|
||||
color: this.isHighScore
|
||||
? this.props.theme.colors.gameGold
|
||||
: this.props.theme.colors.primary
|
||||
}}>
|
||||
{this.isHighScore
|
||||
? i18n.t("screens.game.newHighScore")
|
||||
: i18n.t("screens.game.gameOver.text")}
|
||||
</Headline>
|
||||
<Divider/>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: 10,
|
||||
marginBottom: 10,
|
||||
}}>
|
||||
<Text style={{
|
||||
fontSize: 20,
|
||||
}}>
|
||||
{i18n.t("screens.game.score", {score: stats.score})}
|
||||
</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={'star'}
|
||||
color={this.props.theme.colors.tetrisScore}
|
||||
size={30}
|
||||
style={{
|
||||
marginLeft: 5
|
||||
}}/>
|
||||
</View>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
}}>
|
||||
<Text>{i18n.t("screens.game.level")}</Text>
|
||||
<MaterialCommunityIcons
|
||||
style={{
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
}}
|
||||
name={"gamepad-square"}
|
||||
size={20}
|
||||
color={this.props.theme.colors.textDisabled}
|
||||
/>
|
||||
<Text>
|
||||
{stats.level}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
}}>
|
||||
<Text>{i18n.t("screens.game.time")}</Text>
|
||||
<MaterialCommunityIcons
|
||||
style={{
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
}}
|
||||
name={"timer"}
|
||||
size={20}
|
||||
color={this.props.theme.colors.textDisabled}
|
||||
/>
|
||||
<Text>
|
||||
{stats.time}
|
||||
</Text>
|
||||
</View>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
getWelcomeText() {
|
||||
return (
|
||||
<View>
|
||||
|
@ -109,7 +249,14 @@ class GameStartScreen extends React.Component<Props, State> {
|
|||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
}}/>
|
||||
<SpeechArrow
|
||||
style={{marginLeft: "60%"}}
|
||||
size={20}
|
||||
color={this.props.theme.colors.mascotMessageArrow}
|
||||
/>
|
||||
<Card style={{
|
||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
||||
borderWidth: 2,
|
||||
marginLeft: 10,
|
||||
marginRight: 10,
|
||||
}}>
|
||||
|
@ -131,20 +278,133 @@ class GameStartScreen extends React.Component<Props, State> {
|
|||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
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 (
|
||||
<View style={{
|
||||
marginLeft: place === 2 ? 20 : "auto",
|
||||
marginRight: place === 3 ? 20 : "auto",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
}}>
|
||||
{
|
||||
this.isHighScore && place === 1
|
||||
?
|
||||
<Animatable.View
|
||||
animation={"swing"}
|
||||
iterationCount={"infinite"}
|
||||
duration={2000}
|
||||
delay={1000}
|
||||
useNativeDriver={true}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: -20
|
||||
}}
|
||||
>
|
||||
<Animatable.View
|
||||
animation={"pulse"}
|
||||
iterationCount={"infinite"}
|
||||
useNativeDriver={true}
|
||||
>
|
||||
<MaterialCommunityIcons
|
||||
name={"decagram"}
|
||||
color={this.props.theme.colors.gameGold}
|
||||
size={150}
|
||||
/>
|
||||
</Animatable.View>
|
||||
</Animatable.View>
|
||||
|
||||
: null
|
||||
}
|
||||
<MaterialCommunityIcons
|
||||
name={icon}
|
||||
color={this.isHighScore && place === 1 ? "#fff" : color}
|
||||
size={size}
|
||||
/>
|
||||
<Text style={{
|
||||
textAlign: "center",
|
||||
fontWeight: place === 1 ? "bold" : null,
|
||||
fontSize: fontSize,
|
||||
}}>{score}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<View style={{
|
||||
marginBottom: 20,
|
||||
marginTop: 20
|
||||
}}>
|
||||
{this.getPodiumRender(1, gold.toString())}
|
||||
<View style={{
|
||||
flexDirection: "row",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
}}>
|
||||
{this.getPodiumRender(3, bronze.toString())}
|
||||
{this.getPodiumRender(2, silver.toString())}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
getMainContent() {
|
||||
return (
|
||||
<LinearGradient
|
||||
style={{flex: 1}}
|
||||
colors={[
|
||||
this.props.theme.colors.background + "00",
|
||||
this.props.theme.colors.background
|
||||
]}
|
||||
start={{x: 0, y: 0.1}}
|
||||
end={{x: 0.1, y: 1}}
|
||||
>
|
||||
<View style={{flex: 1}}>
|
||||
{this.getPiecesBackground()}
|
||||
{this.getWelcomeText()}
|
||||
{
|
||||
this.gameStats != null
|
||||
? this.getPostGameContent(this.gameStats)
|
||||
: this.getWelcomeText()
|
||||
}
|
||||
<Button
|
||||
icon={"play"}
|
||||
mode={"contained"}
|
||||
onPress={() => this.props.navigation.navigate("game-main")}
|
||||
onPress={() => this.props.navigation.replace(
|
||||
"game-main",
|
||||
{
|
||||
highScore: this.scores.length > 0
|
||||
? this.scores[0]
|
||||
: null
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
|
@ -153,6 +413,20 @@ class GameStartScreen extends React.Component<Props, State> {
|
|||
>
|
||||
{i18n.t("screens.game.play")}
|
||||
</Button>
|
||||
{this.getTopScoresRender()}
|
||||
</View>
|
||||
</LinearGradient>
|
||||
)
|
||||
}
|
||||
|
||||
keyExtractor = (item: number) => item.toString();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
{this.getPiecesBackground()}
|
||||
<ScrollView>
|
||||
{this.getMainContent()}
|
||||
<MascotPopup
|
||||
visible={this.state.mascotDialogVisible}
|
||||
title={i18n.t("screens.game.mascotDialog.title")}
|
||||
|
@ -168,7 +442,9 @@ class GameStartScreen extends React.Component<Props, State> {
|
|||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue