Compare commits
No commits in common. "b2ff90855f4c974d66eaa2a830842e8a9e722514" and "ca107356d1848a31785d07c91aad763a68ab15a3" have entirely different histories.
b2ff90855f
...
ca107356d1
38 changed files with 655 additions and 1392 deletions
|
|
@ -363,14 +363,6 @@
|
|||
},
|
||||
"game": {
|
||||
"title": "Game",
|
||||
"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}",
|
||||
"highScore": "High score: %{score}",
|
||||
"newHighScore": "New High Score!",
|
||||
"time": "Time:",
|
||||
"level": "Level:",
|
||||
"pause": "Game Paused",
|
||||
"pauseMessage": "The game is paused",
|
||||
"resume": "Resume",
|
||||
|
|
@ -387,11 +379,6 @@
|
|||
"level": "Level: ",
|
||||
"time": "Time: ",
|
||||
"exit": "leave Game"
|
||||
},
|
||||
"mascotDialog": {
|
||||
"title": "Play !",
|
||||
"message": "Play.",
|
||||
"button": "Yes !"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
|
|
|
|||
|
|
@ -361,24 +361,16 @@
|
|||
"homeButtonSubtitle": "Contacte le développeur de l'appli"
|
||||
},
|
||||
"game": {
|
||||
"title": "Jeu trop ouf",
|
||||
"welcomeTitle": "Bienvenue !",
|
||||
"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 :",
|
||||
"title": "Le jeu trop ouf",
|
||||
"pause": "Pause",
|
||||
"pauseMessage": "T'as fait pause, t'es nul",
|
||||
"pauseMessage": "Le jeu est en pause",
|
||||
"resume": "Continuer",
|
||||
"restart": {
|
||||
"text": "Redémarrer",
|
||||
"confirm": "T'es sûr de vouloir redémarrer ?",
|
||||
"confirm": "Est-tu sûr de vouloir redémarrer ?",
|
||||
"confirmMessage": "Tout ton progrès sera perdu, continuer ?",
|
||||
"confirmYes": "Oui",
|
||||
"confirmNo": "Oula non"
|
||||
"confirmNo": "Non"
|
||||
},
|
||||
"gameOver": {
|
||||
"text": "Game Over",
|
||||
|
|
@ -386,11 +378,6 @@
|
|||
"level": "Niveau: ",
|
||||
"time": "Temps: ",
|
||||
"exit": "Quitter"
|
||||
},
|
||||
"mascotDialog": {
|
||||
"title": "Jeu !",
|
||||
"message": "Jouer.",
|
||||
"button": "Oui !"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
import i18n from "i18n-js";
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
|
|
@ -24,7 +23,7 @@ class AlertDialog extends React.PureComponent<Props> {
|
|||
<Paragraph>{this.props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<Button onPress={this.props.onDismiss}>{i18n.t("dialog.ok")}</Button>
|
||||
<Button onPress={this.props.onDismiss}>OK</Button>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Button, Dialog, Paragraph, Portal} from 'react-native-paper';
|
||||
import {FlatList} from "react-native";
|
||||
|
||||
export type OptionsDialogButton = {
|
||||
title: string,
|
||||
onPress: () => void,
|
||||
}
|
||||
|
||||
type Props = {
|
||||
visible: boolean,
|
||||
title: string,
|
||||
message: string,
|
||||
buttons: Array<OptionsDialogButton>,
|
||||
onDismiss: () => void,
|
||||
}
|
||||
|
||||
class OptionsDialog extends React.PureComponent<Props> {
|
||||
|
||||
getButtonRender = ({item}: { item: OptionsDialogButton }) => {
|
||||
return <Button
|
||||
onPress={item.onPress}>
|
||||
{item.title}
|
||||
</Button>;
|
||||
}
|
||||
|
||||
keyExtractor = (item: OptionsDialogButton) => item.title;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Portal>
|
||||
<Dialog
|
||||
visible={this.props.visible}
|
||||
onDismiss={this.props.onDismiss}>
|
||||
<Dialog.Title>{this.props.title}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<Paragraph>{this.props.message}</Paragraph>
|
||||
</Dialog.Content>
|
||||
<Dialog.Actions>
|
||||
<FlatList
|
||||
data={this.props.buttons}
|
||||
renderItem={this.getButtonRender}
|
||||
keyExtractor={this.keyExtractor}
|
||||
horizontal={true}
|
||||
inverted={true}
|
||||
/>
|
||||
</Dialog.Actions>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default OptionsDialog;
|
||||
|
|
@ -3,10 +3,9 @@
|
|||
import * as React from 'react';
|
||||
import * as Animatable from "react-native-animatable";
|
||||
import {Image, TouchableWithoutFeedback, View} from "react-native";
|
||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
||||
|
||||
type Props = {
|
||||
style?: ViewStyle,
|
||||
size: number,
|
||||
emotion: number,
|
||||
animated: boolean,
|
||||
entryAnimation: Animatable.AnimatableProperties | null,
|
||||
|
|
@ -117,10 +116,9 @@ class Mascot extends React.Component<Props, State> {
|
|||
|
||||
if (this.props.onPress == null) {
|
||||
this.onPress = (viewRef: AnimatableViewRef) => {
|
||||
let ref = viewRef.current;
|
||||
if (ref != null) {
|
||||
if (viewRef.current != null) {
|
||||
this.setState({currentEmotion: MASCOT_STYLE.LOVE});
|
||||
ref.rubberBand(1500).then(() => {
|
||||
viewRef.current.rubberBand(1500).then(() => {
|
||||
this.setState({currentEmotion: this.initialEmotion});
|
||||
});
|
||||
|
||||
|
|
@ -132,10 +130,9 @@ class Mascot extends React.Component<Props, State> {
|
|||
|
||||
if (this.props.onLongPress == null) {
|
||||
this.onLongPress = (viewRef: AnimatableViewRef) => {
|
||||
let ref = viewRef.current;
|
||||
if (ref != null) {
|
||||
if (viewRef.current != null) {
|
||||
this.setState({currentEmotion: MASCOT_STYLE.ANGRY});
|
||||
ref.tada(1000).then(() => {
|
||||
viewRef.current.tada(1000).then(() => {
|
||||
this.setState({currentEmotion: this.initialEmotion});
|
||||
});
|
||||
|
||||
|
|
@ -156,8 +153,8 @@ class Mascot extends React.Component<Props, State> {
|
|||
position: "absolute",
|
||||
top: "15%",
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
|
|
@ -171,8 +168,8 @@ class Mascot extends React.Component<Props, State> {
|
|||
position: "absolute",
|
||||
top: "15%",
|
||||
left: isRight ? "-11%" : "11%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
transform: [{rotateY: rotation}]
|
||||
}}
|
||||
/>
|
||||
|
|
@ -184,8 +181,8 @@ class Mascot extends React.Component<Props, State> {
|
|||
key={"container"}
|
||||
style={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
width: this.props.size,
|
||||
height: this.props.size,
|
||||
}}/>);
|
||||
if (emotion === MASCOT_STYLE.CUTE) {
|
||||
final.push(this.getEye(EYE_STYLE.CUTE, true));
|
||||
|
|
@ -220,13 +217,14 @@ class Mascot extends React.Component<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const size = this.props.size;
|
||||
const entryAnimation = this.props.animated ? this.props.entryAnimation : null;
|
||||
const loopAnimation = this.props.animated ? this.props.loopAnimation : null;
|
||||
return (
|
||||
<Animatable.View
|
||||
style={{
|
||||
aspectRatio: 1,
|
||||
...this.props.style
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
{...entryAnimation}
|
||||
>
|
||||
|
|
@ -243,8 +241,8 @@ class Mascot extends React.Component<Props, State> {
|
|||
<Image
|
||||
source={MASCOT_IMAGE}
|
||||
style={{
|
||||
width: "100%",
|
||||
height:"100%",
|
||||
width: size,
|
||||
height: size,
|
||||
}}
|
||||
/>
|
||||
{this.getEyes(this.state.currentEmotion)}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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,
|
||||
|
|
@ -103,11 +102,19 @@ class MascotPopup extends React.Component<Props, State> {
|
|||
animation={this.props.visible ? "bounceInLeft" : "bounceOutLeft"}
|
||||
duration={this.props.visible ? 1000 : 300}
|
||||
>
|
||||
<SpeechArrow
|
||||
style={{marginLeft: this.mascotSize / 3}}
|
||||
size={20}
|
||||
color={this.props.theme.colors.mascotMessageArrow}
|
||||
/>
|
||||
<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,
|
||||
}}/>
|
||||
<Card style={{
|
||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
||||
borderWidth: 4,
|
||||
|
|
@ -153,7 +160,7 @@ class MascotPopup extends React.Component<Props, State> {
|
|||
duration={this.props.visible ? 1500 : 200}
|
||||
>
|
||||
<Mascot
|
||||
style={{width: this.mascotSize}}
|
||||
size={this.mascotSize}
|
||||
animated={true}
|
||||
emotion={this.props.emotion}
|
||||
/>
|
||||
|
|
@ -234,16 +241,15 @@ class MascotPopup extends React.Component<Props, State> {
|
|||
}}>
|
||||
<View style={{
|
||||
marginTop: -80,
|
||||
width: "100%"
|
||||
}}>
|
||||
{this.getMascot()}
|
||||
{this.getSpeechBubble()}
|
||||
</View>
|
||||
|
||||
</View>
|
||||
</Portal>
|
||||
)
|
||||
;
|
||||
|
||||
</View>
|
||||
</Portal>
|
||||
)
|
||||
;
|
||||
} else
|
||||
return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
// @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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -146,27 +146,20 @@ export default class CustomIntroSlider extends React.Component<Props, State> {
|
|||
</View>
|
||||
<Animatable.View
|
||||
animation={"fadeIn"}>
|
||||
{index !== 0 && index !== this.introSlides.length - 1
|
||||
?
|
||||
<Mascot
|
||||
{index !== 0 && index !== this.introSlides.length -1
|
||||
? <Animatable.View
|
||||
animation={"pulse"}
|
||||
iterationCount={"infinite"}
|
||||
duration={2000}
|
||||
style={{
|
||||
marginLeft: 30,
|
||||
marginBottom: 0,
|
||||
width: 100,
|
||||
marginTop: -30,
|
||||
}}
|
||||
emotion={item.mascotStyle}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "slideInLeft",
|
||||
duration: 500
|
||||
}}
|
||||
loopAnimation={{
|
||||
animation: "pulse",
|
||||
iterationCount: "infinite",
|
||||
duration: 2000,
|
||||
}}
|
||||
/> : null}
|
||||
}}>
|
||||
<Mascot emotion={item.mascotStyle} size={100}/>
|
||||
</Animatable.View> : null}
|
||||
|
||||
<View style={{
|
||||
marginLeft: 50,
|
||||
width: 0,
|
||||
|
|
@ -211,23 +204,23 @@ export default class CustomIntroSlider extends React.Component<Props, State> {
|
|||
getEndView = () => {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
height: "80%"
|
||||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "slideInDown",
|
||||
duration: 2000,
|
||||
}}
|
||||
loopAnimation={{
|
||||
animation: "pulse",
|
||||
duration: 2000,
|
||||
iterationCount: "infinite"
|
||||
}}
|
||||
/>
|
||||
<View
|
||||
style={styles.center}>
|
||||
<Mascot
|
||||
size={250}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "slideInDown",
|
||||
duration: 2000,
|
||||
}}
|
||||
loopAnimation={{
|
||||
animation: "pulse",
|
||||
duration: 2000,
|
||||
iterationCount: "infinite"
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
|
@ -235,52 +228,55 @@ export default class CustomIntroSlider extends React.Component<Props, State> {
|
|||
getWelcomeView = () => {
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Mascot
|
||||
style={{
|
||||
...styles.center,
|
||||
height: "80%"
|
||||
}}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "bounceIn",
|
||||
duration: 2000,
|
||||
}}
|
||||
/>
|
||||
<Animatable.Text
|
||||
useNativeDriver={true}
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
<View
|
||||
style={styles.center}>
|
||||
<Mascot
|
||||
size={250}
|
||||
emotion={MASCOT_STYLE.NORMAL}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "bounceIn",
|
||||
duration: 2000,
|
||||
}}
|
||||
/>
|
||||
<Animatable.Text
|
||||
useNativeDriver={true}
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
|
||||
style={{
|
||||
style={{
|
||||
color: "#fff",
|
||||
textAlign: "center",
|
||||
fontSize: 25,
|
||||
}}>
|
||||
PABLO
|
||||
</Animatable.Text>
|
||||
<Animatable.View
|
||||
useNativeDriver={true}
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
delay={200}
|
||||
PABLO
|
||||
</Animatable.Text>
|
||||
<Animatable.View
|
||||
useNativeDriver={true}
|
||||
animation={"fadeInUp"}
|
||||
duration={500}
|
||||
delay={200}
|
||||
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 30,
|
||||
right: "20%",
|
||||
width: 50,
|
||||
height: 50,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
style={{
|
||||
...styles.center,
|
||||
transform: [{rotateZ: "70deg"}],
|
||||
}}
|
||||
name={"undo"}
|
||||
color={'#fff'}
|
||||
size={40}/>
|
||||
</Animatable.View>
|
||||
position: "absolute",
|
||||
top: 210,
|
||||
left: 160,
|
||||
width: 50,
|
||||
height: 50,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: "auto",
|
||||
marginBottom: "auto",
|
||||
transform: [{rotateZ: "70deg"}],
|
||||
}}
|
||||
name={"undo"}
|
||||
color={'#fff'}
|
||||
size={40}/>
|
||||
</Animatable.View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
|
@ -407,5 +403,5 @@ const styles = StyleSheet.create({
|
|||
marginBottom: 'auto',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class CustomTabBar extends React.Component<Props, State> {
|
|||
canPreventDefault: true,
|
||||
});
|
||||
if (route.name === "home" && !event.defaultPrevented)
|
||||
this.props.navigation.navigate('game-start');
|
||||
this.props.navigation.navigate('tetris');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -100,11 +100,6 @@ export default class AsyncStorageManager {
|
|||
default: '1',
|
||||
current: '',
|
||||
},
|
||||
gameStartShowBanner: {
|
||||
key: 'gameStartShowBanner',
|
||||
default: '1',
|
||||
current: '',
|
||||
},
|
||||
proxiwashWatchedMachines: {
|
||||
key: 'proxiwashWatchedMachines',
|
||||
default: '[]',
|
||||
|
|
@ -136,11 +131,6 @@ export default class AsyncStorageManager {
|
|||
]),
|
||||
current: '',
|
||||
},
|
||||
gameScores: {
|
||||
key: 'gameScores',
|
||||
default: '[]',
|
||||
current: '',
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
// @flow
|
||||
|
||||
import AsyncStorageManager from "./AsyncStorageManager";
|
||||
import {DarkTheme, DefaultTheme} from 'react-native-paper';
|
||||
import {DarkTheme, DefaultTheme, Theme} from 'react-native-paper';
|
||||
import AprilFoolsManager from "./AprilFoolsManager";
|
||||
import {Appearance} from 'react-native-appearance';
|
||||
|
||||
const colorScheme = Appearance.getColorScheme();
|
||||
|
||||
export type CustomTheme = {
|
||||
...DefaultTheme,
|
||||
...Theme,
|
||||
colors: {
|
||||
primary: string,
|
||||
accent: string,
|
||||
|
|
@ -56,10 +56,6 @@ export type CustomTheme = {
|
|||
tetrisJ: string,
|
||||
tetrisL: string,
|
||||
|
||||
gameGold: string,
|
||||
gameSilver: string,
|
||||
gameBronze: string,
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: string,
|
||||
},
|
||||
|
|
@ -123,7 +119,8 @@ export default class ThemeManager {
|
|||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#f0f0f0',
|
||||
tetrisBackground: '#e6e6e6',
|
||||
tetrisBorder: '#2f2f2f',
|
||||
tetrisScore: '#e2bd33',
|
||||
tetrisI: '#3cd9e6',
|
||||
tetrisO: '#ffdd00',
|
||||
|
|
@ -133,10 +130,6 @@ export default class ThemeManager {
|
|||
tetrisJ: '#2a67e3',
|
||||
tetrisL: '#da742d',
|
||||
|
||||
gameGold: "#ffd610",
|
||||
gameSilver: "#7b7b7b",
|
||||
gameBronze: "#a15218",
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: "#dedede",
|
||||
},
|
||||
|
|
@ -189,7 +182,8 @@ export default class ThemeManager {
|
|||
tutorinsaColor: '#f93943',
|
||||
|
||||
// Tetris
|
||||
tetrisBackground: '#181818',
|
||||
tetrisBackground: '#2c2c2c',
|
||||
tetrisBorder: '#1b1b1b',
|
||||
tetrisScore: '#e2d707',
|
||||
tetrisI: '#30b3be',
|
||||
tetrisO: '#c1a700',
|
||||
|
|
@ -199,10 +193,6 @@ export default class ThemeManager {
|
|||
tetrisJ: '#0f37b9',
|
||||
tetrisL: '#b96226',
|
||||
|
||||
gameGold: "#ffd610",
|
||||
gameSilver: "#7b7b7b",
|
||||
gameBronze: "#a15218",
|
||||
|
||||
// Mascot Popup
|
||||
mascotMessageArrow: "#323232",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import DebugScreen from '../screens/About/DebugScreen';
|
|||
import {createStackNavigator, TransitionPresets} from "@react-navigation/stack";
|
||||
import i18n from "i18n-js";
|
||||
import TabNavigator from "./TabNavigator";
|
||||
import GameMainScreen from "../screens/Game/screens/GameMainScreen";
|
||||
import TetrisScreen from "../screens/Tetris/TetrisScreen";
|
||||
import VoteScreen from "../screens/Amicale/VoteScreen";
|
||||
import LoginScreen from "../screens/Amicale/LoginScreen";
|
||||
import {Platform} from "react-native";
|
||||
|
|
@ -27,8 +27,6 @@ import EquipmentScreen from "../screens/Amicale/Equipment/EquipmentListScreen";
|
|||
import EquipmentLendScreen from "../screens/Amicale/Equipment/EquipmentRentScreen";
|
||||
import EquipmentConfirmScreen from "../screens/Amicale/Equipment/EquipmentConfirmScreen";
|
||||
import DashboardEditScreen from "../screens/Other/Settings/DashboardEditScreen";
|
||||
import GameStartScreen from "../screens/Game/screens/GameStartScreen";
|
||||
import GameEndScreen from "../screens/Game/screens/GameEndScreen";
|
||||
|
||||
const modalTransition = Platform.OS === 'ios' ? TransitionPresets.ModalPresentationIOS : TransitionPresets.ModalSlideFromBottomIOS;
|
||||
|
||||
|
|
@ -94,22 +92,8 @@ function MainStackComponent(props: { createTabNavigator: () => React.Node }) {
|
|||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="game-start"
|
||||
component={GameStartScreen}
|
||||
options={{
|
||||
title: i18n.t("screens.game.title"),
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="game-main"
|
||||
component={GameMainScreen}
|
||||
options={{
|
||||
title: i18n.t("screens.game.title"),
|
||||
}}
|
||||
/>
|
||||
<MainStack.Screen
|
||||
name="game-end"
|
||||
component={GameEndScreen}
|
||||
name="tetris"
|
||||
component={TetrisScreen}
|
||||
options={{
|
||||
title: i18n.t("screens.game.title"),
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -117,10 +117,8 @@ function HomeStackComponent(initialRoute: string | null, defaultData: { [key: st
|
|||
},
|
||||
headerTitle: (props) => <View style={{flexDirection: "row"}}>
|
||||
<Mascot
|
||||
style={{
|
||||
width: 50
|
||||
}}
|
||||
emotion={MASCOT_STYLE.RANDOM}
|
||||
size={50}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "bounceIn",
|
||||
|
|
|
|||
|
|
@ -158,18 +158,15 @@ class ProfileScreen extends React.Component<Props, State> {
|
|||
<Card style={styles.card}>
|
||||
<Card.Title
|
||||
title={i18n.t("screens.profile.welcomeTitle", {name: this.data.first_name})}
|
||||
left={() =>
|
||||
<Mascot
|
||||
style={{
|
||||
width: 60
|
||||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "bounceIn",
|
||||
duration: 1000
|
||||
}}
|
||||
/>}
|
||||
left={() => <Mascot
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
size={60}
|
||||
animated={true}
|
||||
entryAnimation={{
|
||||
animation: "bounceIn",
|
||||
duration: 1000
|
||||
}}
|
||||
/>}
|
||||
titleStyle={{marginLeft: 10}}
|
||||
/>
|
||||
<Card.Content>
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import type {Grid} from "./GridComponent";
|
||||
import GridComponent from "./GridComponent";
|
||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
||||
|
||||
type Props = {
|
||||
items: Array<Grid>,
|
||||
style: ViewStyle
|
||||
}
|
||||
|
||||
class Preview extends React.PureComponent<Props> {
|
||||
|
||||
getGrids() {
|
||||
let grids = [];
|
||||
for (let i = 0; i < this.props.items.length; i++) {
|
||||
grids.push(this.getGridRender(this.props.items[i], i));
|
||||
}
|
||||
return grids;
|
||||
}
|
||||
|
||||
getGridRender(item: Grid, index: number) {
|
||||
return <GridComponent
|
||||
width={item[0].length}
|
||||
height={item.length}
|
||||
grid={item}
|
||||
style={{
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
marginBottom: 5,
|
||||
}}
|
||||
key={index.toString()}
|
||||
/>;
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.items.length > 0) {
|
||||
return (
|
||||
<View style={this.props.style}>
|
||||
{this.getGrids()}
|
||||
</View>
|
||||
);
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(Preview);
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from "react";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
import {withTheme} from "react-native-paper";
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
type State = {
|
||||
|
||||
}
|
||||
|
||||
class GameEndScreen extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(GameEndScreen);
|
||||
|
|
@ -1,424 +0,0 @@
|
|||
// @flow
|
||||
|
||||
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 GameLogic from "../logic/GameLogic";
|
||||
import type {Grid} from "../components/GridComponent";
|
||||
import GridComponent from "../components/GridComponent";
|
||||
import Preview from "../components/Preview";
|
||||
import i18n from "i18n-js";
|
||||
import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
import type {OptionsDialogButton} from "../../../components/Dialogs/OptionsDialog";
|
||||
import OptionsDialog from "../../../components/Dialogs/OptionsDialog";
|
||||
|
||||
type Props = {
|
||||
navigation: StackNavigationProp,
|
||||
route: { params: { highScore: number }, ... },
|
||||
theme: CustomTheme,
|
||||
}
|
||||
|
||||
type State = {
|
||||
grid: Grid,
|
||||
gameRunning: boolean,
|
||||
gameTime: number,
|
||||
gameScore: number,
|
||||
gameLevel: number,
|
||||
|
||||
dialogVisible: boolean,
|
||||
dialogTitle: string,
|
||||
dialogMessage: string,
|
||||
dialogButtons: Array<OptionsDialogButton>,
|
||||
onDialogDismiss: () => void,
|
||||
}
|
||||
|
||||
class GameMainScreen extends React.Component<Props, State> {
|
||||
|
||||
logic: GameLogic;
|
||||
highScore: number | null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.logic = new GameLogic(20, 10, this.props.theme);
|
||||
this.state = {
|
||||
grid: this.logic.getCurrentGrid(),
|
||||
gameRunning: false,
|
||||
gameTime: 0,
|
||||
gameScore: 0,
|
||||
gameLevel: 0,
|
||||
dialogVisible: false,
|
||||
dialogTitle: "",
|
||||
dialogMessage: "",
|
||||
dialogButtons: [],
|
||||
onDialogDismiss: () => {
|
||||
},
|
||||
};
|
||||
if (this.props.route.params != null)
|
||||
this.highScore = this.props.route.params.highScore;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.navigation.setOptions({
|
||||
headerRight: this.getRightButton,
|
||||
});
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
getRightButton = () => {
|
||||
return <MaterialHeaderButtons>
|
||||
<Item title="pause" iconName="pause" onPress={this.togglePause}/>
|
||||
</MaterialHeaderButtons>;
|
||||
}
|
||||
|
||||
getFormattedTime(seconds: number) {
|
||||
let 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();
|
||||
return format;
|
||||
}
|
||||
|
||||
onTick = (score: number, level: number, newGrid: Grid) => {
|
||||
this.setState({
|
||||
gameScore: score,
|
||||
gameLevel: level,
|
||||
grid: newGrid,
|
||||
});
|
||||
}
|
||||
|
||||
onClock = (time: number) => {
|
||||
this.setState({
|
||||
gameTime: time,
|
||||
});
|
||||
}
|
||||
|
||||
updateGrid = (newGrid: Grid) => {
|
||||
this.setState({
|
||||
grid: newGrid,
|
||||
});
|
||||
}
|
||||
|
||||
updateGridScore = (newGrid: Grid, score: number) => {
|
||||
this.setState({
|
||||
grid: newGrid,
|
||||
gameScore: score,
|
||||
});
|
||||
}
|
||||
|
||||
togglePause = () => {
|
||||
this.logic.togglePause();
|
||||
if (this.logic.isGamePaused())
|
||||
this.showPausePopup();
|
||||
}
|
||||
|
||||
onDialogDismiss = () => this.setState({dialogVisible: false});
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
showGameOverConfirm() {
|
||||
let message = i18n.t("screens.game.gameOver.score") + this.state.gameScore + '\n';
|
||||
message += i18n.t("screens.game.gameOver.level") + this.state.gameLevel + '\n';
|
||||
message += i18n.t("screens.game.gameOver.time") + this.getFormattedTime(this.state.gameTime) + '\n';
|
||||
const onDismiss = () => {
|
||||
this.onDialogDismiss();
|
||||
this.startGame();
|
||||
};
|
||||
this.setState({
|
||||
dialogVisible: true,
|
||||
dialogTitle: i18n.t("screens.game.gameOver.text"),
|
||||
dialogMessage: message,
|
||||
dialogButtons: [
|
||||
{
|
||||
title: i18n.t("screens.game.gameOver.exit"),
|
||||
onPress: () => this.props.navigation.goBack()
|
||||
},
|
||||
{
|
||||
title: i18n.t("screens.game.resume"),
|
||||
onPress: onDismiss
|
||||
}
|
||||
],
|
||||
onDialogDismiss: onDismiss,
|
||||
});
|
||||
}
|
||||
|
||||
startGame = () => {
|
||||
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
||||
this.setState({
|
||||
gameRunning: true,
|
||||
});
|
||||
}
|
||||
|
||||
onGameEnd = (time: number, score: number, isRestart: boolean) => {
|
||||
this.setState({
|
||||
gameTime: time,
|
||||
gameScore: score,
|
||||
gameRunning: false,
|
||||
});
|
||||
if (!isRestart)
|
||||
this.props.navigation.replace(
|
||||
"game-start",
|
||||
{
|
||||
score: this.state.gameScore,
|
||||
level: this.state.gameLevel,
|
||||
time: this.state.gameTime,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
getStatusIcons() {
|
||||
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={this.props.theme.colors.subtitle}
|
||||
size={20}/>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
color: this.props.theme.colors.subtitle
|
||||
}}>{this.getFormattedTime(this.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={this.props.theme.colors.text}
|
||||
size={20}/>
|
||||
<Text style={{
|
||||
marginLeft: 5
|
||||
}}>{this.state.gameLevel}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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",
|
||||
}}>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
fontSize: 20,
|
||||
}}>{i18n.t("screens.game.score", {score: this.state.gameScore})}</Text>
|
||||
<MaterialCommunityIcons
|
||||
name={'star'}
|
||||
color={this.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: 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>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
getControlButtons() {
|
||||
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={this.props.theme.colors.tetrisScore}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
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={this.state.grid}
|
||||
style={{
|
||||
backgroundColor: this.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={this.state.dialogVisible}
|
||||
title={this.state.dialogTitle}
|
||||
message={this.state.dialogMessage}
|
||||
buttons={this.state.dialogButtons}
|
||||
onDismiss={this.state.onDialogDismiss}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(GameMainScreen);
|
||||
|
|
@ -1,453 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import * as React from "react";
|
||||
import {StackNavigationProp} from "@react-navigation/stack";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
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";
|
||||
import AsyncStorageManager from "../../../managers/AsyncStorageManager";
|
||||
import type {Grid} from "../components/GridComponent";
|
||||
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,
|
||||
}
|
||||
|
||||
type State = {
|
||||
mascotDialogVisible: boolean,
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
AsyncStorageManager.getInstance().savePref(
|
||||
AsyncStorageManager.getInstance().preferences.gameStartShowBanner.key,
|
||||
'0'
|
||||
);
|
||||
this.setState({mascotDialogVisible: false})
|
||||
};
|
||||
|
||||
getPiecesBackground() {
|
||||
let gridList = [];
|
||||
for (let i = 0; i < 18; i++) {
|
||||
gridList.push(this.gridManager.getEmptyGrid(4, 4));
|
||||
const piece = new Piece(this.props.theme);
|
||||
piece.toGrid(gridList[i], true);
|
||||
}
|
||||
return (
|
||||
<View style={{
|
||||
position: "absolute",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}>
|
||||
{gridList.map((item: Grid, 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 (
|
||||
<Animatable.View
|
||||
animation={"fadeInDownBig"}
|
||||
delay={animDelay}
|
||||
duration={animDuration}
|
||||
key={index.toString()}
|
||||
style={{
|
||||
width: size + "%",
|
||||
position: "absolute",
|
||||
top: top + "%",
|
||||
left: left + "%",
|
||||
}}
|
||||
>
|
||||
<View style={{
|
||||
transform: [{rotateZ: rot + "deg"}],
|
||||
}}>
|
||||
<GridComponent
|
||||
width={4}
|
||||
height={4}
|
||||
grid={item}
|
||||
style={{
|
||||
marginRight: 5,
|
||||
marginLeft: 5,
|
||||
marginBottom: 5,
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
</Animatable.View>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
<Mascot emotion={MASCOT_STYLE.COOL} style={{
|
||||
width: "40%",
|
||||
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,
|
||||
}}>
|
||||
<Card.Content>
|
||||
<Headline
|
||||
style={{
|
||||
textAlign: "center",
|
||||
color: this.props.theme.colors.primary
|
||||
}}>
|
||||
{i18n.t("screens.game.welcomeTitle")}
|
||||
</Headline>
|
||||
<Divider/>
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 10,
|
||||
}}>
|
||||
{i18n.t("screens.game.welcomeMessage")}
|
||||
</Paragraph>
|
||||
</Card.Content>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
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.gameStats != null
|
||||
? this.getPostGameContent(this.gameStats)
|
||||
: this.getWelcomeText()
|
||||
}
|
||||
<Button
|
||||
icon={"play"}
|
||||
mode={"contained"}
|
||||
onPress={() => this.props.navigation.replace(
|
||||
"game-main",
|
||||
{
|
||||
highScore: this.scores.length > 0
|
||||
? this.scores[0]
|
||||
: null
|
||||
}
|
||||
)}
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
{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")}
|
||||
message={i18n.t("screens.game.mascotDialog.message")}
|
||||
icon={"gamepad-variant"}
|
||||
buttons={{
|
||||
action: null,
|
||||
cancel: {
|
||||
message: i18n.t("screens.game.mascotDialog.button"),
|
||||
icon: "check",
|
||||
onPress: this.hideMascotDialog,
|
||||
}
|
||||
}}
|
||||
emotion={MASCOT_STYLE.COOL}
|
||||
/>
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTheme(GameStartScreen);
|
||||
|
||||
|
|
@ -3,7 +3,6 @@
|
|||
import Piece from "./Piece";
|
||||
import ScoreManager from "./ScoreManager";
|
||||
import GridManager from "./GridManager";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class GameLogic {
|
||||
|
||||
|
|
@ -46,20 +45,20 @@ export default class GameLogic {
|
|||
#onClock: Function;
|
||||
endCallback: Function;
|
||||
|
||||
#theme: CustomTheme;
|
||||
#colors: Object;
|
||||
|
||||
constructor(height: number, width: number, theme: CustomTheme) {
|
||||
constructor(height: number, width: number, colors: Object) {
|
||||
this.#height = height;
|
||||
this.#width = width;
|
||||
this.#gameRunning = false;
|
||||
this.#gamePaused = false;
|
||||
this.#theme = theme;
|
||||
this.#colors = colors;
|
||||
this.#autoRepeatActivationDelay = 300;
|
||||
this.#autoRepeatDelay = 50;
|
||||
this.#nextPieces = [];
|
||||
this.#nextPiecesCount = 3;
|
||||
this.#scoreManager = new ScoreManager();
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||
}
|
||||
|
||||
getHeight(): number {
|
||||
|
|
@ -145,7 +144,7 @@ export default class GameLogic {
|
|||
callback(this.#gridManager.getCurrentGrid());
|
||||
}
|
||||
this.#pressInInterval = setTimeout(() =>
|
||||
this.movePressedRepeat(false, callback, x, y),
|
||||
this.movePressedRepeat(false, callback, x, y),
|
||||
isInitial ? this.#autoRepeatActivationDelay : this.#autoRepeatDelay
|
||||
);
|
||||
}
|
||||
|
|
@ -166,8 +165,7 @@ export default class GameLogic {
|
|||
getNextPiecesPreviews() {
|
||||
let finalArray = [];
|
||||
for (let i = 0; i < this.#nextPieces.length; i++) {
|
||||
const gridSize = this.#nextPieces[i].getCurrentShape().getCurrentShape()[0].length;
|
||||
finalArray.push(this.#gridManager.getEmptyGrid(gridSize, gridSize));
|
||||
finalArray.push(this.#gridManager.getEmptyGrid(4, 4));
|
||||
this.#nextPieces[i].toGrid(finalArray[i], true);
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +179,7 @@ export default class GameLogic {
|
|||
|
||||
generateNextPieces() {
|
||||
while (this.#nextPieces.length < this.#nextPiecesCount) {
|
||||
this.#nextPieces.push(new Piece(this.#theme));
|
||||
this.#nextPieces.push(new Piece(this.#colors));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +219,7 @@ export default class GameLogic {
|
|||
this.#gameTime = 0;
|
||||
this.#scoreManager = new ScoreManager();
|
||||
this.#gameTick = GameLogic.levelTicks[this.#scoreManager.getLevel()];
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||
this.#nextPieces = [];
|
||||
this.generateNextPieces();
|
||||
this.createTetromino();
|
||||
|
|
@ -2,37 +2,39 @@
|
|||
|
||||
import Piece from "./Piece";
|
||||
import ScoreManager from "./ScoreManager";
|
||||
import type {Coordinates} from '../Shapes/BaseShape';
|
||||
import type {Grid} from "../components/GridComponent";
|
||||
import type {Cell} from "../components/CellComponent";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
import type {coordinates} from './Shapes/BaseShape';
|
||||
|
||||
|
||||
export type cell = {color: string, isEmpty: boolean, key: string};
|
||||
export type grid = Array<Array<cell>>;
|
||||
|
||||
/**
|
||||
* Class used to manage the game grid
|
||||
*
|
||||
*/
|
||||
export default class GridManager {
|
||||
|
||||
#currentGrid: Grid;
|
||||
#theme: CustomTheme;
|
||||
#currentGrid: grid;
|
||||
#colors: Object;
|
||||
|
||||
/**
|
||||
* Initializes a grid of the given size
|
||||
*
|
||||
* @param width The grid width
|
||||
* @param height The grid height
|
||||
* @param theme Object containing current theme
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
constructor(width: number, height: number, theme: CustomTheme) {
|
||||
this.#theme = theme;
|
||||
constructor(width: number, height: number, colors: Object) {
|
||||
this.#colors = colors;
|
||||
this.#currentGrid = this.getEmptyGrid(height, width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current grid
|
||||
*
|
||||
* @return {Grid} The current grid
|
||||
* @return {grid} The current grid
|
||||
*/
|
||||
getCurrentGrid(): Grid {
|
||||
getCurrentGrid(): grid {
|
||||
return this.#currentGrid;
|
||||
}
|
||||
|
||||
|
|
@ -40,13 +42,13 @@ export default class GridManager {
|
|||
* Get a new empty grid line of the given size
|
||||
*
|
||||
* @param width The line size
|
||||
* @return {Array<Cell>}
|
||||
* @return {Array<cell>}
|
||||
*/
|
||||
getEmptyLine(width: number): Array<Cell> {
|
||||
getEmptyLine(width: number): Array<cell> {
|
||||
let line = [];
|
||||
for (let col = 0; col < width; col++) {
|
||||
line.push({
|
||||
color: this.#theme.colors.tetrisBackground,
|
||||
color: this.#colors.tetrisBackground,
|
||||
isEmpty: true,
|
||||
key: col.toString(),
|
||||
});
|
||||
|
|
@ -59,9 +61,9 @@ export default class GridManager {
|
|||
*
|
||||
* @param width The grid width
|
||||
* @param height The grid height
|
||||
* @return {Grid} A new empty grid
|
||||
* @return {grid} A new empty grid
|
||||
*/
|
||||
getEmptyGrid(height: number, width: number): Grid {
|
||||
getEmptyGrid(height: number, width: number): grid {
|
||||
let grid = [];
|
||||
for (let row = 0; row < height; row++) {
|
||||
grid.push(this.getEmptyLine(width));
|
||||
|
|
@ -89,21 +91,21 @@ export default class GridManager {
|
|||
* Gets the lines to clear around the given piece's coordinates.
|
||||
* The piece's coordinates are used for optimization and to prevent checking the whole grid.
|
||||
*
|
||||
* @param pos The piece's coordinates to check lines at
|
||||
* @param coord The piece's coordinates to check lines at
|
||||
* @return {Array<number>} An array containing the line numbers to clear
|
||||
*/
|
||||
getLinesToClear(pos: Array<Coordinates>): Array<number> {
|
||||
getLinesToClear(coord: Array<coordinates>): Array<number> {
|
||||
let rows = [];
|
||||
for (let i = 0; i < pos.length; i++) {
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
let isLineFull = true;
|
||||
for (let col = 0; col < this.#currentGrid[pos[i].y].length; col++) {
|
||||
if (this.#currentGrid[pos[i].y][col].isEmpty) {
|
||||
for (let col = 0; col < this.#currentGrid[coord[i].y].length; col++) {
|
||||
if (this.#currentGrid[coord[i].y][col].isEmpty) {
|
||||
isLineFull = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isLineFull && rows.indexOf(pos[i].y) === -1)
|
||||
rows.push(pos[i].y);
|
||||
if (isLineFull && rows.indexOf(coord[i].y) === -1)
|
||||
rows.push(coord[i].y);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
import ShapeL from "../Shapes/ShapeL";
|
||||
import ShapeI from "../Shapes/ShapeI";
|
||||
import ShapeJ from "../Shapes/ShapeJ";
|
||||
import ShapeO from "../Shapes/ShapeO";
|
||||
import ShapeS from "../Shapes/ShapeS";
|
||||
import ShapeT from "../Shapes/ShapeT";
|
||||
import ShapeZ from "../Shapes/ShapeZ";
|
||||
import type {Coordinates} from '../Shapes/BaseShape';
|
||||
import BaseShape from "../Shapes/BaseShape";
|
||||
import type {Grid} from "../components/GridComponent";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
import ShapeL from "./Shapes/ShapeL";
|
||||
import ShapeI from "./Shapes/ShapeI";
|
||||
import ShapeJ from "./Shapes/ShapeJ";
|
||||
import ShapeO from "./Shapes/ShapeO";
|
||||
import ShapeS from "./Shapes/ShapeS";
|
||||
import ShapeT from "./Shapes/ShapeT";
|
||||
import ShapeZ from "./Shapes/ShapeZ";
|
||||
import type {coordinates} from './Shapes/BaseShape';
|
||||
import type {grid} from './GridManager';
|
||||
|
||||
/**
|
||||
* Class used as an abstraction layer for shapes.
|
||||
|
|
@ -26,26 +24,26 @@ export default class Piece {
|
|||
ShapeT,
|
||||
ShapeZ,
|
||||
];
|
||||
#currentShape: BaseShape;
|
||||
#theme: CustomTheme;
|
||||
#currentShape: Object;
|
||||
#colors: Object;
|
||||
|
||||
/**
|
||||
* Initializes this piece's color and shape
|
||||
*
|
||||
* @param theme Object containing current theme
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
constructor(theme: CustomTheme) {
|
||||
this.#currentShape = this.getRandomShape(theme);
|
||||
this.#theme = theme;
|
||||
constructor(colors: Object) {
|
||||
this.#currentShape = this.getRandomShape(colors);
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random shape object
|
||||
*
|
||||
* @param theme Object containing current theme
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
getRandomShape(theme: CustomTheme) {
|
||||
return new this.#shapes[Math.floor(Math.random() * 7)](theme);
|
||||
getRandomShape(colors: Object) {
|
||||
return new this.#shapes[Math.floor(Math.random() * 7)](colors);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -53,13 +51,13 @@ export default class Piece {
|
|||
*
|
||||
* @param grid The grid to remove the piece from
|
||||
*/
|
||||
removeFromGrid(grid: Grid) {
|
||||
const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < pos.length; i++) {
|
||||
grid[pos[i].y][pos[i].x] = {
|
||||
color: this.#theme.colors.tetrisBackground,
|
||||
removeFromGrid(grid: grid) {
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
grid[coord[i].y][coord[i].x] = {
|
||||
color: this.#colors.tetrisBackground,
|
||||
isEmpty: true,
|
||||
key: grid[pos[i].y][pos[i].x].key
|
||||
key: grid[coord[i].y][coord[i].x].key
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -70,13 +68,13 @@ export default class Piece {
|
|||
* @param grid The grid to add the piece to
|
||||
* @param isPreview Should we use this piece's current position to determine the cells?
|
||||
*/
|
||||
toGrid(grid: Grid, isPreview: boolean) {
|
||||
const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(!isPreview);
|
||||
for (let i = 0; i < pos.length; i++) {
|
||||
grid[pos[i].y][pos[i].x] = {
|
||||
toGrid(grid: grid, isPreview: boolean) {
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(!isPreview);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
grid[coord[i].y][coord[i].x] = {
|
||||
color: this.#currentShape.getColor(),
|
||||
isEmpty: false,
|
||||
key: grid[pos[i].y][pos[i].x].key
|
||||
key: grid[coord[i].y][coord[i].x].key
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -89,15 +87,15 @@ export default class Piece {
|
|||
* @param height The grid's height
|
||||
* @return {boolean} If the position is valid
|
||||
*/
|
||||
isPositionValid(grid: Grid, width: number, height: number) {
|
||||
isPositionValid(grid: grid, width: number, height: number) {
|
||||
let isValid = true;
|
||||
const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < pos.length; i++) {
|
||||
if (pos[i].x >= width
|
||||
|| pos[i].x < 0
|
||||
|| pos[i].y >= height
|
||||
|| pos[i].y < 0
|
||||
|| !grid[pos[i].y][pos[i].x].isEmpty) {
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
if (coord[i].x >= width
|
||||
|| coord[i].x < 0
|
||||
|| coord[i].y >= height
|
||||
|| coord[i].y < 0
|
||||
|| !grid[coord[i].y][coord[i].x].isEmpty) {
|
||||
isValid = false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -116,7 +114,7 @@ export default class Piece {
|
|||
* @param freezeCallback Callback to use if the piece should freeze itself
|
||||
* @return {boolean} True if the move was valid, false otherwise
|
||||
*/
|
||||
tryMove(x: number, y: number, grid: Grid, width: number, height: number, freezeCallback: () => void) {
|
||||
tryMove(x: number, y: number, grid: grid, width: number, height: number, freezeCallback: Function) {
|
||||
if (x > 1) x = 1; // Prevent moving from more than one tile
|
||||
if (x < -1) x = -1;
|
||||
if (y > 1) y = 1;
|
||||
|
|
@ -145,7 +143,7 @@ export default class Piece {
|
|||
* @param height The grid's height
|
||||
* @return {boolean} True if the rotation was valid, false otherwise
|
||||
*/
|
||||
tryRotate(grid: Grid, width: number, height: number) {
|
||||
tryRotate(grid: grid, width: number, height: number) {
|
||||
this.removeFromGrid(grid);
|
||||
this.#currentShape.rotate(true);
|
||||
if (!this.isPositionValid(grid, width, height)) {
|
||||
|
|
@ -160,13 +158,9 @@ export default class Piece {
|
|||
/**
|
||||
* Gets this piece used cells coordinates
|
||||
*
|
||||
* @return {Array<Coordinates>} An array of coordinates
|
||||
* @return {Array<coordinates>} An array of coordinates
|
||||
*/
|
||||
getCoordinates(): Array<Coordinates> {
|
||||
getCoordinates(): Array<coordinates> {
|
||||
return this.#currentShape.getCellsCoordinates(true);
|
||||
}
|
||||
|
||||
getCurrentShape() {
|
||||
return this.#currentShape;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,10 @@
|
|||
// @flow
|
||||
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export type Coordinates = {
|
||||
export type coordinates = {
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
type Shape = Array<Array<number>>;
|
||||
|
||||
/**
|
||||
* Abstract class used to represent a BaseShape.
|
||||
* Abstract classes do not exist by default in Javascript: we force it by throwing errors in the constructor
|
||||
|
|
@ -16,18 +12,16 @@ type Shape = Array<Array<number>>;
|
|||
*/
|
||||
export default class BaseShape {
|
||||
|
||||
#currentShape: Shape;
|
||||
#currentShape: Array<Array<number>>;
|
||||
#rotation: number;
|
||||
position: Coordinates;
|
||||
theme: CustomTheme;
|
||||
position: coordinates;
|
||||
|
||||
/**
|
||||
* Prevent instantiation if classname is BaseShape to force class to be abstract
|
||||
*/
|
||||
constructor(theme: CustomTheme) {
|
||||
constructor() {
|
||||
if (this.constructor === BaseShape)
|
||||
throw new Error("Abstract class can't be instantiated");
|
||||
this.theme = theme;
|
||||
this.#rotation = 0;
|
||||
this.position = {x: 0, y: 0};
|
||||
this.#currentShape = this.getShapes()[this.#rotation];
|
||||
|
|
@ -47,14 +41,16 @@ export default class BaseShape {
|
|||
*
|
||||
* Used by tests to read private fields
|
||||
*/
|
||||
getShapes(): Array<Shape> {
|
||||
getShapes(): Array<Array<Array<number>>> {
|
||||
throw new Error("Method 'getShapes()' must be implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this object's current shape.
|
||||
*
|
||||
* Used by tests to read private fields
|
||||
*/
|
||||
getCurrentShape(): Shape {
|
||||
getCurrentShape(): Array<Array<number>> {
|
||||
return this.#currentShape;
|
||||
}
|
||||
|
||||
|
|
@ -63,9 +59,9 @@ export default class BaseShape {
|
|||
* This will return an array of coordinates representing the positions of the cells used by this object.
|
||||
*
|
||||
* @param isAbsolute Should we take into account the current position of the object?
|
||||
* @return {Array<Coordinates>} This object cells coordinates
|
||||
* @return {Array<coordinates>} This object cells coordinates
|
||||
*/
|
||||
getCellsCoordinates(isAbsolute: boolean): Array<Coordinates> {
|
||||
getCellsCoordinates(isAbsolute: boolean): Array<coordinates> {
|
||||
let coordinates = [];
|
||||
for (let row = 0; row < this.#currentShape.length; row++) {
|
||||
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeI extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisI;
|
||||
return this.#colors.tetrisI;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeJ extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisJ;
|
||||
return this.#colors.tetrisJ;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeL extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisL;
|
||||
return this.#colors.tetrisL;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeO extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 4;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisO;
|
||||
return this.#colors.tetrisO;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeS extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisS;
|
||||
return this.#colors.tetrisS;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeT extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisT;
|
||||
return this.#colors.tetrisT;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
// @flow
|
||||
|
||||
import BaseShape from "./BaseShape";
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export default class ShapeZ extends BaseShape {
|
||||
|
||||
constructor(theme: CustomTheme) {
|
||||
super(theme);
|
||||
#colors: Object;
|
||||
|
||||
constructor(colors: Object) {
|
||||
super();
|
||||
this.position.x = 3;
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
getColor(): string {
|
||||
return this.theme.colors.tetrisZ;
|
||||
return this.#colors.tetrisZ;
|
||||
}
|
||||
|
||||
getShapes() {
|
||||
299
src/screens/Tetris/TetrisScreen.js
Normal file
299
src/screens/Tetris/TetrisScreen.js
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {Alert, View} from 'react-native';
|
||||
import {IconButton, Text, withTheme} from 'react-native-paper';
|
||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import GameLogic from "./GameLogic";
|
||||
import Grid from "./components/Grid";
|
||||
import Preview from "./components/Preview";
|
||||
import i18n from "i18n-js";
|
||||
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
}
|
||||
|
||||
type State = {
|
||||
grid: Array<Array<Object>>,
|
||||
gameRunning: boolean,
|
||||
gameTime: number,
|
||||
gameScore: number,
|
||||
gameLevel: number,
|
||||
}
|
||||
|
||||
class TetrisScreen extends React.Component<Props, State> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
logic: GameLogic;
|
||||
onTick: Function;
|
||||
onClock: Function;
|
||||
onGameEnd: Function;
|
||||
updateGrid: Function;
|
||||
updateGridScore: Function;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
this.logic = new GameLogic(20, 10, this.colors);
|
||||
this.state = {
|
||||
grid: this.logic.getCurrentGrid(),
|
||||
gameRunning: false,
|
||||
gameTime: 0,
|
||||
gameScore: 0,
|
||||
gameLevel: 0,
|
||||
};
|
||||
this.onTick = this.onTick.bind(this);
|
||||
this.onClock = this.onClock.bind(this);
|
||||
this.onGameEnd = this.onGameEnd.bind(this);
|
||||
this.updateGrid = this.updateGrid.bind(this);
|
||||
this.updateGridScore = this.updateGridScore.bind(this);
|
||||
this.props.navigation.addListener('blur', this.onScreenBlur.bind(this));
|
||||
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const rightButton = this.getRightButton.bind(this);
|
||||
this.props.navigation.setOptions({
|
||||
headerRight: rightButton,
|
||||
});
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
getRightButton() {
|
||||
return <MaterialHeaderButtons>
|
||||
<Item title="pause" iconName="pause" onPress={() => this.togglePause()}/>
|
||||
</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);
|
||||
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();
|
||||
return format;
|
||||
}
|
||||
|
||||
onTick(score: number, level: number, newGrid: Array<Array<Object>>) {
|
||||
this.setState({
|
||||
gameScore: score,
|
||||
gameLevel: level,
|
||||
grid: newGrid,
|
||||
});
|
||||
}
|
||||
|
||||
onClock(time: number) {
|
||||
this.setState({
|
||||
gameTime: time,
|
||||
});
|
||||
}
|
||||
|
||||
updateGrid(newGrid: Array<Array<Object>>) {
|
||||
this.setState({
|
||||
grid: newGrid,
|
||||
});
|
||||
}
|
||||
|
||||
updateGridScore(newGrid: Array<Array<Object>>, score: number) {
|
||||
this.setState({
|
||||
grid: newGrid,
|
||||
gameScore: score,
|
||||
});
|
||||
}
|
||||
|
||||
togglePause() {
|
||||
this.logic.togglePause();
|
||||
if (this.logic.isGamePaused())
|
||||
this.showPausePopup();
|
||||
}
|
||||
|
||||
showPausePopup() {
|
||||
Alert.alert(
|
||||
i18n.t("screens.game.pause"),
|
||||
i18n.t("screens.game.pauseMessage"),
|
||||
[
|
||||
{text: i18n.t("screens.game.restart.text"), onPress: () => this.showRestartConfirm()},
|
||||
{text: i18n.t("screens.game.resume"), onPress: () => this.togglePause()},
|
||||
],
|
||||
{cancelable: false},
|
||||
);
|
||||
}
|
||||
|
||||
showRestartConfirm() {
|
||||
Alert.alert(
|
||||
i18n.t("screens.game.restart.confirm"),
|
||||
i18n.t("screens.game.restart.confirmMessage"),
|
||||
[
|
||||
{text: i18n.t("screens.game.restart.confirmNo"), onPress: () => this.showPausePopup()},
|
||||
{text: i18n.t("screens.game.restart.confirmYes"), onPress: () => this.startGame()},
|
||||
],
|
||||
{cancelable: false},
|
||||
);
|
||||
}
|
||||
|
||||
showGameOverConfirm() {
|
||||
let message = i18n.t("screens.game.gameOver.score") + this.state.gameScore + '\n';
|
||||
message += i18n.t("screens.game.gameOver.level") + this.state.gameLevel + '\n';
|
||||
message += i18n.t("screens.game.gameOver.time") + this.getFormattedTime(this.state.gameTime) + '\n';
|
||||
Alert.alert(
|
||||
i18n.t("screens.game.gameOver.text"),
|
||||
message,
|
||||
[
|
||||
{text: i18n.t("screens.game.gameOver.exit"), onPress: () => this.props.navigation.goBack()},
|
||||
{text: i18n.t("screens.game.restart.text"), onPress: () => this.startGame()},
|
||||
],
|
||||
{cancelable: false},
|
||||
);
|
||||
}
|
||||
|
||||
startGame() {
|
||||
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
||||
this.setState({
|
||||
gameRunning: true,
|
||||
});
|
||||
}
|
||||
|
||||
onGameEnd(time: number, score: number, isRestart: boolean) {
|
||||
this.setState({
|
||||
gameTime: time,
|
||||
gameScore: score,
|
||||
gameRunning: false,
|
||||
});
|
||||
if (!isRestart)
|
||||
this.showGameOverConfirm();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
top: 5,
|
||||
left: 10,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
name={'timer'}
|
||||
color={this.colors.subtitle}
|
||||
size={20}/>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
color: this.colors.subtitle
|
||||
}}>{this.getFormattedTime(this.state.gameTime)}</Text>
|
||||
</View>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
top: 50,
|
||||
left: 10,
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
name={'gamepad'}
|
||||
color={this.colors.text}
|
||||
size={20}/>
|
||||
<Text style={{
|
||||
marginLeft: 5
|
||||
}}>{this.state.gameLevel}</Text>
|
||||
</View>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
marginRight: 'auto',
|
||||
marginLeft: 'auto',
|
||||
}}>
|
||||
<MaterialCommunityIcons
|
||||
name={'star'}
|
||||
color={this.colors.tetrisScore}
|
||||
size={30}/>
|
||||
<Text style={{
|
||||
marginLeft: 5,
|
||||
fontSize: 22,
|
||||
}}>{this.state.gameScore}</Text>
|
||||
</View>
|
||||
<Grid
|
||||
width={this.logic.getWidth()}
|
||||
height={this.logic.getHeight()}
|
||||
containerMaxHeight={'80%'}
|
||||
containerMaxWidth={'60%'}
|
||||
grid={this.state.grid}
|
||||
backgroundColor={this.colors.tetrisBackground}
|
||||
/>
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
top: 50,
|
||||
right: 5,
|
||||
}}>
|
||||
<Preview
|
||||
next={this.logic.getNextPiecesPreviews()}
|
||||
/>
|
||||
</View>
|
||||
<View style={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
flexDirection: 'row',
|
||||
width: '100%',
|
||||
}}>
|
||||
<IconButton
|
||||
icon="rotate-right-variant"
|
||||
size={40}
|
||||
onPress={() => this.logic.rotatePressed(this.updateGrid)}
|
||||
style={{marginRight: 'auto'}}
|
||||
/>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
}}>
|
||||
<IconButton
|
||||
icon="arrow-left"
|
||||
size={40}
|
||||
onPress={() => this.logic.pressedOut()}
|
||||
onPressIn={() => this.logic.leftPressedIn(this.updateGrid)}
|
||||
|
||||
/>
|
||||
<IconButton
|
||||
icon="arrow-right"
|
||||
size={40}
|
||||
onPress={() => this.logic.pressedOut()}
|
||||
onPressIn={() => this.logic.rightPressed(this.updateGrid)}
|
||||
/>
|
||||
</View>
|
||||
<IconButton
|
||||
icon="arrow-down"
|
||||
size={40}
|
||||
onPressIn={() => this.logic.downPressedIn(this.updateGridScore)}
|
||||
onPress={() => this.logic.pressedOut()}
|
||||
style={{marginLeft: 'auto'}}
|
||||
color={this.colors.tetrisScore}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(TetrisScreen);
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import GridManager from "../logic/GridManager";
|
||||
import ScoreManager from "../logic/ScoreManager";
|
||||
import Piece from "../logic/Piece";
|
||||
import GridManager from "../GridManager";
|
||||
import ScoreManager from "../ScoreManager";
|
||||
import Piece from "../Piece";
|
||||
|
||||
let colors = {
|
||||
tetrisBackground: "#000002"
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import Piece from "../logic/Piece";
|
||||
import Piece from "../Piece";
|
||||
import ShapeI from "../Shapes/ShapeI";
|
||||
|
||||
let colors = {
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import ScoreManager from "../logic/ScoreManager";
|
||||
import ScoreManager from "../ScoreManager";
|
||||
|
||||
|
||||
test('incrementScore', () => {
|
||||
|
|
@ -3,26 +3,30 @@
|
|||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
||||
|
||||
export type Cell = {color: string, isEmpty: boolean, key: string};
|
||||
|
||||
type Props = {
|
||||
cell: Cell,
|
||||
theme: CustomTheme,
|
||||
item: Object
|
||||
}
|
||||
|
||||
class CellComponent extends React.PureComponent<Props> {
|
||||
class Cell extends React.PureComponent<Props> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
render() {
|
||||
const item = this.props.cell;
|
||||
const item = this.props.item;
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: item.isEmpty ? 'transparent' : item.color,
|
||||
borderColor: 'transparent',
|
||||
borderRadius: 4,
|
||||
borderColor: item.isEmpty ? 'transparent' : this.colors.tetrisBorder,
|
||||
borderStyle: 'solid',
|
||||
borderRadius: 2,
|
||||
borderWidth: 1,
|
||||
aspectRatio: 1,
|
||||
}}
|
||||
|
|
@ -34,4 +38,4 @@ class CellComponent extends React.PureComponent<Props> {
|
|||
|
||||
}
|
||||
|
||||
export default withTheme(CellComponent);
|
||||
export default withTheme(Cell);
|
||||
|
|
@ -3,26 +3,35 @@
|
|||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import type {Cell} from "./CellComponent";
|
||||
import CellComponent from "./CellComponent";
|
||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
||||
|
||||
export type Grid = Array<Array<CellComponent>>;
|
||||
import Cell from "./Cell";
|
||||
|
||||
type Props = {
|
||||
navigation: Object,
|
||||
grid: Array<Array<Object>>,
|
||||
backgroundColor: string,
|
||||
height: number,
|
||||
width: number,
|
||||
style: ViewStyle,
|
||||
containerMaxHeight: number | string,
|
||||
containerMaxWidth: number | string,
|
||||
}
|
||||
|
||||
class GridComponent extends React.Component<Props> {
|
||||
class Grid extends React.Component<Props> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
getRow(rowNumber: number) {
|
||||
let cells = this.props.grid[rowNumber].map(this.getCellRender);
|
||||
return (
|
||||
<View
|
||||
style={{flexDirection: 'row',}}
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
backgroundColor: this.props.backgroundColor,
|
||||
}}
|
||||
key={rowNumber.toString()}
|
||||
>
|
||||
{cells}
|
||||
|
|
@ -30,8 +39,8 @@ class GridComponent extends React.Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
getCellRender = (item: Cell) => {
|
||||
return <CellComponent cell={item}/>;
|
||||
getCellRender = (item: Object) => {
|
||||
return <Cell item={item} key={item.key}/>;
|
||||
};
|
||||
|
||||
getGrid() {
|
||||
|
|
@ -45,9 +54,12 @@ class GridComponent extends React.Component<Props> {
|
|||
render() {
|
||||
return (
|
||||
<View style={{
|
||||
flexDirection: 'column',
|
||||
maxWidth: this.props.containerMaxWidth,
|
||||
maxHeight: this.props.containerMaxHeight,
|
||||
aspectRatio: this.props.width / this.props.height,
|
||||
borderRadius: 4,
|
||||
...this.props.style
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
}}>
|
||||
{this.getGrid()}
|
||||
</View>
|
||||
|
|
@ -55,4 +67,4 @@ class GridComponent extends React.Component<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default withTheme(GridComponent);
|
||||
export default withTheme(Grid);
|
||||
57
src/screens/Tetris/components/Preview.js
Normal file
57
src/screens/Tetris/components/Preview.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import {View} from 'react-native';
|
||||
import {withTheme} from 'react-native-paper';
|
||||
import Grid from "./Grid";
|
||||
|
||||
type Props = {
|
||||
next: Object,
|
||||
}
|
||||
|
||||
class Preview extends React.PureComponent<Props> {
|
||||
|
||||
colors: Object;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.colors = props.theme.colors;
|
||||
}
|
||||
|
||||
getGrids() {
|
||||
let grids = [];
|
||||
for (let i = 0; i < this.props.next.length; i++) {
|
||||
grids.push(
|
||||
this.getGridRender(this.props.next[i], i)
|
||||
);
|
||||
}
|
||||
return grids;
|
||||
}
|
||||
|
||||
getGridRender(item: Object, index: number) {
|
||||
return <Grid
|
||||
width={item[0].length}
|
||||
height={item.length}
|
||||
grid={item}
|
||||
containerMaxHeight={50}
|
||||
containerMaxWidth={50}
|
||||
backgroundColor={'transparent'}
|
||||
key={index.toString()}
|
||||
/>;
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.props.next.length > 0) {
|
||||
return (
|
||||
<View>
|
||||
{this.getGrids()}
|
||||
</View>
|
||||
);
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
export default withTheme(Preview);
|
||||
Loading…
Reference in a new issue