Application Android et IOS pour l'amicale des élèves https://play.google.com/store/apps/details?id=fr.amicaleinsat.application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

GameMainScreen.tsx 7.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
  20. import { StyleSheet, View } from 'react-native';
  21. import { useTheme } from 'react-native-paper';
  22. import i18n from 'i18n-js';
  23. import { StackNavigationProp } from '@react-navigation/stack';
  24. import GameLogic from '../logic/GameLogic';
  25. import type { GridType } from '../components/GridComponent';
  26. import GridComponent from '../components/GridComponent';
  27. import Preview from '../components/Preview';
  28. import MaterialHeaderButtons, {
  29. Item,
  30. } from '../../../components/Overrides/CustomHeaderButton';
  31. import type { OptionsDialogButtonType } from '../../../components/Dialogs/OptionsDialog';
  32. import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
  33. import GENERAL_STYLES from '../../../constants/Styles';
  34. import { MainRoutes } from '../../../navigation/MainNavigator';
  35. import GameStatus from '../components/GameStatus';
  36. import GameControls from '../components/GameControls';
  37. import GameScore from '../components/GameScore';
  38. import { usePreferences } from '../../../context/preferencesContext';
  39. import {
  40. getPreferenceObject,
  41. PreferenceKeys,
  42. } from '../../../utils/asyncStorage';
  43. import { useFocusEffect, useNavigation } from '@react-navigation/core';
  44. const styles = StyleSheet.create({
  45. container: {
  46. flex: 1,
  47. flexDirection: 'row',
  48. },
  49. gridContainer: {
  50. flex: 4,
  51. },
  52. controlsContainer: {
  53. height: 80,
  54. flexDirection: 'row',
  55. },
  56. directionsContainer: {
  57. flexDirection: 'row',
  58. flex: 4,
  59. },
  60. preview: {
  61. marginLeft: 'auto',
  62. marginRight: 'auto',
  63. marginTop: 10,
  64. },
  65. });
  66. export default function GameMainScreen() {
  67. const theme = useTheme();
  68. const navigation = useNavigation<StackNavigationProp<any>>();
  69. const logic = useRef(new GameLogic(20, 10, theme));
  70. const [gameTime, setGameTime] = useState(0);
  71. const [gameState, setGameState] = useState({
  72. grid: logic.current.getCurrentGrid(),
  73. gameScore: 0,
  74. gameLevel: 0,
  75. });
  76. const [dialogContent, setDialogContent] =
  77. useState<{
  78. dialogTitle: string;
  79. dialogMessage: string;
  80. dialogButtons: Array<OptionsDialogButtonType>;
  81. onDialogDismiss: () => void;
  82. }>();
  83. const { preferences, updatePreferences } = usePreferences();
  84. function getScores() {
  85. const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as
  86. | Array<number>
  87. | undefined;
  88. if (pref) {
  89. return pref.sort((a, b) => b - a);
  90. } else {
  91. return [];
  92. }
  93. }
  94. const savedScores = getScores();
  95. const highScore = savedScores.length > 0 ? savedScores[0] : undefined;
  96. useLayoutEffect(() => {
  97. navigation.setOptions({
  98. headerRight: getRightButton,
  99. });
  100. startGame();
  101. // eslint-disable-next-line react-hooks/exhaustive-deps
  102. }, [navigation]);
  103. useFocusEffect(
  104. useCallback(() => {
  105. const l = logic.current;
  106. return () => l.endGame(true);
  107. }, [])
  108. );
  109. const getRightButton = () => (
  110. <MaterialHeaderButtons>
  111. <Item title={'pause'} iconName={'pause'} onPress={togglePause} />
  112. </MaterialHeaderButtons>
  113. );
  114. const onTick = (score: number, level: number, newGrid: GridType) => {
  115. setGameState({
  116. gameScore: score,
  117. gameLevel: level,
  118. grid: newGrid,
  119. });
  120. };
  121. const onDialogDismiss = () => setDialogContent(undefined);
  122. const onGameEnd = (time: number, score: number, isRestart: boolean) => {
  123. setGameState((prevState) => ({
  124. ...prevState,
  125. gameScore: score,
  126. }));
  127. setGameTime(time);
  128. const newScores = [...savedScores];
  129. const isHighScore = newScores.length === 0 || score > newScores[0];
  130. for (let i = 0; i < 3; i += 1) {
  131. if (newScores.length > i && score > newScores[i]) {
  132. newScores.splice(i, 0, score);
  133. break;
  134. } else if (newScores.length <= i) {
  135. newScores.push(score);
  136. break;
  137. }
  138. }
  139. if (newScores.length > 3) {
  140. newScores.splice(3, 1);
  141. }
  142. if (newScores.some((item, i) => item !== savedScores[i])) {
  143. updatePreferences(PreferenceKeys.gameScores, newScores);
  144. }
  145. if (!isRestart) {
  146. navigation.replace(MainRoutes.GameStart, {
  147. score: score,
  148. level: gameState.gameLevel,
  149. time: time,
  150. isHighScore: isHighScore,
  151. });
  152. }
  153. };
  154. const onDirectionPressed = (newGrid: GridType, score?: number) => {
  155. setGameState((prevState) => ({
  156. ...prevState,
  157. grid: newGrid,
  158. gameScore: score != null ? score : prevState.gameScore,
  159. }));
  160. };
  161. const togglePause = () => {
  162. logic.current.togglePause();
  163. if (logic.current.isGamePaused()) {
  164. showPausePopup();
  165. }
  166. };
  167. const showPausePopup = () => {
  168. const onDismiss = () => {
  169. togglePause();
  170. onDialogDismiss();
  171. };
  172. setDialogContent({
  173. dialogTitle: i18n.t('screens.game.pause'),
  174. dialogMessage: i18n.t('screens.game.pauseMessage'),
  175. dialogButtons: [
  176. {
  177. title: i18n.t('screens.game.restart.text'),
  178. onPress: showRestartConfirm,
  179. },
  180. {
  181. title: i18n.t('screens.game.resume'),
  182. onPress: onDismiss,
  183. },
  184. ],
  185. onDialogDismiss: onDismiss,
  186. });
  187. };
  188. const showRestartConfirm = () => {
  189. setDialogContent({
  190. dialogTitle: i18n.t('screens.game.restart.confirm'),
  191. dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
  192. dialogButtons: [
  193. {
  194. title: i18n.t('screens.game.restart.confirmYes'),
  195. onPress: () => {
  196. onDialogDismiss();
  197. startGame();
  198. },
  199. },
  200. {
  201. title: i18n.t('screens.game.restart.confirmNo'),
  202. onPress: showPausePopup,
  203. },
  204. ],
  205. onDialogDismiss: showPausePopup,
  206. });
  207. };
  208. const startGame = () => {
  209. logic.current.startGame(onTick, setGameTime, onGameEnd);
  210. };
  211. return (
  212. <View style={GENERAL_STYLES.flex}>
  213. <View style={styles.container}>
  214. <GameStatus time={gameTime} level={gameState.gameLevel} />
  215. <View style={styles.gridContainer}>
  216. <GameScore score={gameState.gameScore} highScore={highScore} />
  217. <GridComponent
  218. width={logic.current.getWidth()}
  219. height={logic.current.getHeight()}
  220. grid={gameState.grid}
  221. style={{
  222. backgroundColor: theme.colors.tetrisBackground,
  223. ...GENERAL_STYLES.flex,
  224. ...GENERAL_STYLES.centerHorizontal,
  225. }}
  226. />
  227. </View>
  228. <View style={GENERAL_STYLES.flex}>
  229. <Preview
  230. items={logic.current.getNextPiecesPreviews()}
  231. style={styles.preview}
  232. />
  233. </View>
  234. </View>
  235. <GameControls
  236. logic={logic.current}
  237. onDirectionPressed={onDirectionPressed}
  238. />
  239. {dialogContent ? (
  240. <OptionsDialog
  241. visible={dialogContent !== undefined}
  242. title={dialogContent.dialogTitle}
  243. message={dialogContent.dialogMessage}
  244. buttons={dialogContent.dialogButtons}
  245. onDismiss={dialogContent.onDialogDismiss}
  246. />
  247. ) : null}
  248. </View>
  249. );
  250. }