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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  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] = useState<{
  77. dialogTitle: string;
  78. dialogMessage: string;
  79. dialogButtons: Array<OptionsDialogButtonType>;
  80. onDialogDismiss: () => void;
  81. }>();
  82. const { preferences, updatePreferences } = usePreferences();
  83. function getScores() {
  84. const pref = getPreferenceObject(PreferenceKeys.gameScores, preferences) as
  85. | Array<number>
  86. | undefined;
  87. if (pref) {
  88. return pref.sort((a, b) => b - a);
  89. } else {
  90. return [];
  91. }
  92. }
  93. const savedScores = getScores();
  94. const highScore = savedScores.length > 0 ? savedScores[0] : undefined;
  95. useLayoutEffect(() => {
  96. navigation.setOptions({
  97. headerRight: getRightButton,
  98. });
  99. startGame();
  100. // eslint-disable-next-line react-hooks/exhaustive-deps
  101. }, [navigation]);
  102. useFocusEffect(
  103. useCallback(() => {
  104. const l = logic.current;
  105. return () => l.endGame(true);
  106. }, [])
  107. );
  108. const getRightButton = () => (
  109. <MaterialHeaderButtons>
  110. <Item title={'pause'} iconName={'pause'} onPress={togglePause} />
  111. </MaterialHeaderButtons>
  112. );
  113. const onTick = (score: number, level: number, newGrid: GridType) => {
  114. setGameState({
  115. gameScore: score,
  116. gameLevel: level,
  117. grid: newGrid,
  118. });
  119. };
  120. const onDialogDismiss = () => setDialogContent(undefined);
  121. const onGameEnd = (time: number, score: number, isRestart: boolean) => {
  122. setGameState((prevState) => ({
  123. ...prevState,
  124. gameScore: score,
  125. }));
  126. setGameTime(time);
  127. const newScores = [...savedScores];
  128. const isHighScore = newScores.length === 0 || score > newScores[0];
  129. for (let i = 0; i < 3; i += 1) {
  130. if (newScores.length > i && score > newScores[i]) {
  131. newScores.splice(i, 0, score);
  132. break;
  133. } else if (newScores.length <= i) {
  134. newScores.push(score);
  135. break;
  136. }
  137. }
  138. if (newScores.length > 3) {
  139. newScores.splice(3, 1);
  140. }
  141. if (newScores.some((item, i) => item !== savedScores[i])) {
  142. updatePreferences(PreferenceKeys.gameScores, newScores);
  143. }
  144. if (!isRestart) {
  145. navigation.replace(MainRoutes.GameStart, {
  146. score: score,
  147. level: gameState.gameLevel,
  148. time: time,
  149. isHighScore: isHighScore,
  150. });
  151. }
  152. };
  153. const onDirectionPressed = (newGrid: GridType, score?: number) => {
  154. setGameState((prevState) => ({
  155. ...prevState,
  156. grid: newGrid,
  157. gameScore: score != null ? score : prevState.gameScore,
  158. }));
  159. };
  160. const togglePause = () => {
  161. logic.current.togglePause();
  162. if (logic.current.isGamePaused()) {
  163. showPausePopup();
  164. }
  165. };
  166. const showPausePopup = () => {
  167. const onDismiss = () => {
  168. togglePause();
  169. onDialogDismiss();
  170. };
  171. setDialogContent({
  172. dialogTitle: i18n.t('screens.game.pause'),
  173. dialogMessage: i18n.t('screens.game.pauseMessage'),
  174. dialogButtons: [
  175. {
  176. title: i18n.t('screens.game.restart.text'),
  177. onPress: showRestartConfirm,
  178. },
  179. {
  180. title: i18n.t('screens.game.resume'),
  181. onPress: onDismiss,
  182. },
  183. ],
  184. onDialogDismiss: onDismiss,
  185. });
  186. };
  187. const showRestartConfirm = () => {
  188. setDialogContent({
  189. dialogTitle: i18n.t('screens.game.restart.confirm'),
  190. dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
  191. dialogButtons: [
  192. {
  193. title: i18n.t('screens.game.restart.confirmYes'),
  194. onPress: () => {
  195. onDialogDismiss();
  196. startGame();
  197. },
  198. },
  199. {
  200. title: i18n.t('screens.game.restart.confirmNo'),
  201. onPress: showPausePopup,
  202. },
  203. ],
  204. onDialogDismiss: showPausePopup,
  205. });
  206. };
  207. const startGame = () => {
  208. logic.current.startGame(onTick, setGameTime, onGameEnd);
  209. };
  210. return (
  211. <View style={GENERAL_STYLES.flex}>
  212. <View style={styles.container}>
  213. <GameStatus time={gameTime} level={gameState.gameLevel} />
  214. <View style={styles.gridContainer}>
  215. <GameScore score={gameState.gameScore} highScore={highScore} />
  216. <GridComponent
  217. width={logic.current.getWidth()}
  218. height={logic.current.getHeight()}
  219. grid={gameState.grid}
  220. style={{
  221. backgroundColor: theme.colors.tetrisBackground,
  222. ...GENERAL_STYLES.flex,
  223. ...GENERAL_STYLES.centerHorizontal,
  224. }}
  225. />
  226. </View>
  227. <View style={GENERAL_STYLES.flex}>
  228. <Preview
  229. items={logic.current.getNextPiecesPreviews()}
  230. style={styles.preview}
  231. />
  232. </View>
  233. </View>
  234. <GameControls
  235. logic={logic.current}
  236. onDirectionPressed={onDirectionPressed}
  237. />
  238. {dialogContent ? (
  239. <OptionsDialog
  240. visible={dialogContent !== undefined}
  241. title={dialogContent.dialogTitle}
  242. message={dialogContent.dialogMessage}
  243. buttons={dialogContent.dialogButtons}
  244. onDismiss={dialogContent.onDialogDismiss}
  245. />
  246. ) : null}
  247. </View>
  248. );
  249. }