Application Android et IOS pour l'amicale des élèves
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 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  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 * as React from 'react';
  20. import {View} from 'react-native';
  21. import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
  22. import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
  23. import i18n from 'i18n-js';
  24. import {StackNavigationProp} from '@react-navigation/stack';
  25. import GameLogic from '../logic/GameLogic';
  26. import type {GridType} from '../components/GridComponent';
  27. import GridComponent from '../components/GridComponent';
  28. import Preview from '../components/Preview';
  29. import MaterialHeaderButtons, {
  30. Item,
  31. } from '../../../components/Overrides/CustomHeaderButton';
  32. import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
  33. import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
  34. type PropsType = {
  35. navigation: StackNavigationProp<any>;
  36. route: {params: {highScore: number}};
  37. theme: ReactNativePaper.Theme;
  38. };
  39. type StateType = {
  40. grid: GridType;
  41. gameTime: number;
  42. gameScore: number;
  43. gameLevel: number;
  44. dialogVisible: boolean;
  45. dialogTitle: string;
  46. dialogMessage: string;
  47. dialogButtons: Array<OptionsDialogButtonType>;
  48. onDialogDismiss: () => void;
  49. };
  50. class GameMainScreen extends React.Component<PropsType, StateType> {
  51. static getFormattedTime(seconds: number): string {
  52. const date = new Date();
  53. date.setHours(0);
  54. date.setMinutes(0);
  55. date.setSeconds(seconds);
  56. let format;
  57. if (date.getHours()) {
  58. format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
  59. } else if (date.getMinutes()) {
  60. format = `${date.getMinutes()}:${date.getSeconds()}`;
  61. } else {
  62. format = date.getSeconds().toString();
  63. }
  64. return format;
  65. }
  66. logic: GameLogic;
  67. highScore: number | null;
  68. constructor(props: PropsType) {
  69. super(props);
  70. this.highScore = null;
  71. this.logic = new GameLogic(20, 10, props.theme);
  72. this.state = {
  73. grid: this.logic.getCurrentGrid(),
  74. gameTime: 0,
  75. gameScore: 0,
  76. gameLevel: 0,
  77. dialogVisible: false,
  78. dialogTitle: '',
  79. dialogMessage: '',
  80. dialogButtons: [],
  81. onDialogDismiss: () => {},
  82. };
  83. if (props.route.params != null) {
  84. this.highScore = props.route.params.highScore;
  85. }
  86. }
  87. componentDidMount() {
  88. const {navigation} = this.props;
  89. navigation.setOptions({
  90. headerRight: this.getRightButton,
  91. });
  92. this.startGame();
  93. }
  94. componentWillUnmount() {
  95. this.logic.endGame(true);
  96. }
  97. getRightButton = () => {
  98. return (
  99. <MaterialHeaderButtons>
  100. <Item title="pause" iconName="pause" onPress={this.togglePause} />
  101. </MaterialHeaderButtons>
  102. );
  103. };
  104. onTick = (score: number, level: number, newGrid: GridType) => {
  105. this.setState({
  106. gameScore: score,
  107. gameLevel: level,
  108. grid: newGrid,
  109. });
  110. };
  111. onClock = (time: number) => {
  112. this.setState({
  113. gameTime: time,
  114. });
  115. };
  116. onDialogDismiss = () => {
  117. this.setState({dialogVisible: false});
  118. };
  119. onGameEnd = (time: number, score: number, isRestart: boolean) => {
  120. const {props, state} = this;
  121. this.setState({
  122. gameTime: time,
  123. gameScore: score,
  124. });
  125. if (!isRestart) {
  126. props.navigation.replace('game-start', {
  127. score: state.gameScore,
  128. level: state.gameLevel,
  129. time: state.gameTime,
  130. });
  131. }
  132. };
  133. getStatusIcons() {
  134. const {props, state} = this;
  135. return (
  136. <View
  137. style={{
  138. flex: 1,
  139. marginTop: 'auto',
  140. marginBottom: 'auto',
  141. }}>
  142. <View
  143. style={{
  144. marginLeft: 'auto',
  145. marginRight: 'auto',
  146. }}>
  147. <Caption
  148. style={{
  149. marginLeft: 'auto',
  150. marginRight: 'auto',
  151. marginBottom: 5,
  152. }}>
  153. {i18n.t('screens.game.time')}
  154. </Caption>
  155. <View
  156. style={{
  157. flexDirection: 'row',
  158. }}>
  159. <MaterialCommunityIcons
  160. name="timer"
  161. color={props.theme.colors.subtitle}
  162. size={20}
  163. />
  164. <Text
  165. style={{
  166. marginLeft: 5,
  167. color: props.theme.colors.subtitle,
  168. }}>
  169. {GameMainScreen.getFormattedTime(state.gameTime)}
  170. </Text>
  171. </View>
  172. </View>
  173. <View
  174. style={{
  175. marginLeft: 'auto',
  176. marginRight: 'auto',
  177. marginTop: 20,
  178. }}>
  179. <Caption
  180. style={{
  181. marginLeft: 'auto',
  182. marginRight: 'auto',
  183. marginBottom: 5,
  184. }}>
  185. {i18n.t('screens.game.level')}
  186. </Caption>
  187. <View
  188. style={{
  189. flexDirection: 'row',
  190. }}>
  191. <MaterialCommunityIcons
  192. name="gamepad-square"
  193. color={props.theme.colors.text}
  194. size={20}
  195. />
  196. <Text
  197. style={{
  198. marginLeft: 5,
  199. }}>
  200. {state.gameLevel}
  201. </Text>
  202. </View>
  203. </View>
  204. </View>
  205. );
  206. }
  207. getScoreIcon() {
  208. const {props, state} = this;
  209. const highScore =
  210. this.highScore == null || state.gameScore > this.highScore
  211. ? state.gameScore
  212. : this.highScore;
  213. return (
  214. <View
  215. style={{
  216. marginTop: 10,
  217. marginBottom: 10,
  218. }}>
  219. <View
  220. style={{
  221. flexDirection: 'row',
  222. marginLeft: 'auto',
  223. marginRight: 'auto',
  224. }}>
  225. <Text
  226. style={{
  227. marginLeft: 5,
  228. fontSize: 20,
  229. }}>
  230. {i18n.t('screens.game.score', {score: state.gameScore})}
  231. </Text>
  232. <MaterialCommunityIcons
  233. name="star"
  234. color={props.theme.colors.tetrisScore}
  235. size={20}
  236. style={{
  237. marginTop: 'auto',
  238. marginBottom: 'auto',
  239. marginLeft: 5,
  240. }}
  241. />
  242. </View>
  243. <View
  244. style={{
  245. flexDirection: 'row',
  246. marginLeft: 'auto',
  247. marginRight: 'auto',
  248. marginTop: 5,
  249. }}>
  250. <Text
  251. style={{
  252. marginLeft: 5,
  253. fontSize: 10,
  254. color: props.theme.colors.textDisabled,
  255. }}>
  256. {i18n.t('screens.game.highScore', {score: highScore})}
  257. </Text>
  258. <MaterialCommunityIcons
  259. name="star"
  260. color={props.theme.colors.tetrisScore}
  261. size={10}
  262. style={{
  263. marginTop: 'auto',
  264. marginBottom: 'auto',
  265. marginLeft: 5,
  266. }}
  267. />
  268. </View>
  269. </View>
  270. );
  271. }
  272. getControlButtons() {
  273. const {props} = this;
  274. return (
  275. <View
  276. style={{
  277. height: 80,
  278. flexDirection: 'row',
  279. }}>
  280. <IconButton
  281. icon="rotate-right-variant"
  282. size={40}
  283. onPress={() => {
  284. this.logic.rotatePressed(this.updateGrid);
  285. }}
  286. style={{flex: 1}}
  287. />
  288. <View
  289. style={{
  290. flexDirection: 'row',
  291. flex: 4,
  292. }}>
  293. <IconButton
  294. icon="chevron-left"
  295. size={40}
  296. style={{flex: 1}}
  297. onPress={() => {
  298. this.logic.pressedOut();
  299. }}
  300. onPressIn={() => {
  301. this.logic.leftPressedIn(this.updateGrid);
  302. }}
  303. />
  304. <IconButton
  305. icon="chevron-right"
  306. size={40}
  307. style={{flex: 1}}
  308. onPress={() => {
  309. this.logic.pressedOut();
  310. }}
  311. onPressIn={() => {
  312. this.logic.rightPressed(this.updateGrid);
  313. }}
  314. />
  315. </View>
  316. <IconButton
  317. icon="arrow-down-bold"
  318. size={40}
  319. onPressIn={() => {
  320. this.logic.downPressedIn(this.updateGridScore);
  321. }}
  322. onPress={() => {
  323. this.logic.pressedOut();
  324. }}
  325. style={{flex: 1}}
  326. color={props.theme.colors.tetrisScore}
  327. />
  328. </View>
  329. );
  330. }
  331. updateGrid = (newGrid: GridType) => {
  332. this.setState({
  333. grid: newGrid,
  334. });
  335. };
  336. updateGridScore = (newGrid: GridType, score?: number) => {
  337. this.setState((prevState: StateType): {
  338. grid: GridType;
  339. gameScore: number;
  340. } => ({
  341. grid: newGrid,
  342. gameScore: score != null ? score : prevState.gameScore,
  343. }));
  344. };
  345. togglePause = () => {
  346. this.logic.togglePause();
  347. if (this.logic.isGamePaused()) {
  348. this.showPausePopup();
  349. }
  350. };
  351. showPausePopup = () => {
  352. const onDismiss = () => {
  353. this.togglePause();
  354. this.onDialogDismiss();
  355. };
  356. this.setState({
  357. dialogVisible: true,
  358. dialogTitle: i18n.t('screens.game.pause'),
  359. dialogMessage: i18n.t('screens.game.pauseMessage'),
  360. dialogButtons: [
  361. {
  362. title: i18n.t('screens.game.restart.text'),
  363. onPress: this.showRestartConfirm,
  364. },
  365. {
  366. title: i18n.t('screens.game.resume'),
  367. onPress: onDismiss,
  368. },
  369. ],
  370. onDialogDismiss: onDismiss,
  371. });
  372. };
  373. showRestartConfirm = () => {
  374. this.setState({
  375. dialogVisible: true,
  376. dialogTitle: i18n.t('screens.game.restart.confirm'),
  377. dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
  378. dialogButtons: [
  379. {
  380. title: i18n.t('screens.game.restart.confirmYes'),
  381. onPress: () => {
  382. this.onDialogDismiss();
  383. this.startGame();
  384. },
  385. },
  386. {
  387. title: i18n.t('screens.game.restart.confirmNo'),
  388. onPress: this.showPausePopup,
  389. },
  390. ],
  391. onDialogDismiss: this.showPausePopup,
  392. });
  393. };
  394. startGame = () => {
  395. this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
  396. };
  397. render() {
  398. const {props, state} = this;
  399. return (
  400. <View style={{flex: 1}}>
  401. <View
  402. style={{
  403. flex: 1,
  404. flexDirection: 'row',
  405. }}>
  406. {this.getStatusIcons()}
  407. <View style={{flex: 4}}>
  408. {this.getScoreIcon()}
  409. <GridComponent
  410. width={this.logic.getWidth()}
  411. height={this.logic.getHeight()}
  412. grid={state.grid}
  413. style={{
  414. backgroundColor: props.theme.colors.tetrisBackground,
  415. flex: 1,
  416. marginLeft: 'auto',
  417. marginRight: 'auto',
  418. }}
  419. />
  420. </View>
  421. <View style={{flex: 1}}>
  422. <Preview
  423. items={this.logic.getNextPiecesPreviews()}
  424. style={{
  425. marginLeft: 'auto',
  426. marginRight: 'auto',
  427. marginTop: 10,
  428. }}
  429. />
  430. </View>
  431. </View>
  432. {this.getControlButtons()}
  433. <OptionsDialog
  434. visible={state.dialogVisible}
  435. title={state.dialogTitle}
  436. message={state.dialogMessage}
  437. buttons={state.dialogButtons}
  438. onDismiss={state.onDialogDismiss}
  439. />
  440. </View>
  441. );
  442. }
  443. }
  444. export default withTheme(GameMainScreen);