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.

GameStartScreen.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. // @flow
  2. import * as React from 'react';
  3. import {StackNavigationProp} from '@react-navigation/stack';
  4. import {
  5. Button,
  6. Card,
  7. Divider,
  8. Headline,
  9. Paragraph,
  10. Text,
  11. withTheme,
  12. } from 'react-native-paper';
  13. import {View} from 'react-native';
  14. import i18n from 'i18n-js';
  15. import * as Animatable from 'react-native-animatable';
  16. import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
  17. import LinearGradient from 'react-native-linear-gradient';
  18. import type {CustomThemeType} from '../../../managers/ThemeManager';
  19. import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
  20. import MascotPopup from '../../../components/Mascot/MascotPopup';
  21. import AsyncStorageManager from '../../../managers/AsyncStorageManager';
  22. import type {GridType} from '../components/GridComponent';
  23. import GridComponent from '../components/GridComponent';
  24. import GridManager from '../logic/GridManager';
  25. import Piece from '../logic/Piece';
  26. import SpeechArrow from '../../../components/Mascot/SpeechArrow';
  27. import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
  28. type GameStatsType = {
  29. score: number,
  30. level: number,
  31. time: number,
  32. };
  33. type PropsType = {
  34. navigation: StackNavigationProp,
  35. route: {
  36. params: GameStatsType,
  37. },
  38. theme: CustomThemeType,
  39. };
  40. class GameStartScreen extends React.Component<PropsType> {
  41. gridManager: GridManager;
  42. scores: Array<number>;
  43. gameStats: GameStatsType | null;
  44. isHighScore: boolean;
  45. constructor(props: PropsType) {
  46. super(props);
  47. this.gridManager = new GridManager(4, 4, props.theme);
  48. this.scores = AsyncStorageManager.getObject(
  49. AsyncStorageManager.PREFERENCES.gameScores.key,
  50. );
  51. this.scores.sort((a: number, b: number): number => b - a);
  52. if (props.route.params != null) this.recoverGameScore();
  53. }
  54. getPiecesBackground(): React.Node {
  55. const {theme} = this.props;
  56. const gridList = [];
  57. for (let i = 0; i < 18; i += 1) {
  58. gridList.push(this.gridManager.getEmptyGrid(4, 4));
  59. const piece = new Piece(theme);
  60. piece.toGrid(gridList[i], true);
  61. }
  62. return (
  63. <View
  64. style={{
  65. position: 'absolute',
  66. width: '100%',
  67. height: '100%',
  68. }}>
  69. {gridList.map((item: GridType, index: number): React.Node => {
  70. const size = 10 + Math.floor(Math.random() * 30);
  71. const top = Math.floor(Math.random() * 100);
  72. const rot = Math.floor(Math.random() * 360);
  73. const left = (index % 6) * 20;
  74. const animDelay = size * 20;
  75. const animDuration = 2 * (2000 - size * 30);
  76. return (
  77. <Animatable.View
  78. useNativeDriver
  79. animation="fadeInDownBig"
  80. delay={animDelay}
  81. duration={animDuration}
  82. key={`piece${index.toString()}`}
  83. style={{
  84. width: `${size}%`,
  85. position: 'absolute',
  86. top: `${top}%`,
  87. left: `${left}%`,
  88. }}>
  89. <GridComponent
  90. width={4}
  91. height={4}
  92. grid={item}
  93. style={{
  94. transform: [{rotateZ: `${rot}deg`}],
  95. }}
  96. />
  97. </Animatable.View>
  98. );
  99. })}
  100. </View>
  101. );
  102. }
  103. getPostGameContent(stats: GameStatsType): React.Node {
  104. const {props} = this;
  105. return (
  106. <View
  107. style={{
  108. flex: 1,
  109. }}>
  110. <Mascot
  111. emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
  112. animated={this.isHighScore}
  113. style={{
  114. width: this.isHighScore ? '50%' : '30%',
  115. marginLeft: this.isHighScore ? 'auto' : null,
  116. marginRight: this.isHighScore ? 'auto' : null,
  117. }}
  118. />
  119. <SpeechArrow
  120. style={{marginLeft: this.isHighScore ? '60%' : '20%'}}
  121. size={20}
  122. color={props.theme.colors.mascotMessageArrow}
  123. />
  124. <Card
  125. style={{
  126. borderColor: props.theme.colors.mascotMessageArrow,
  127. borderWidth: 2,
  128. marginLeft: 20,
  129. marginRight: 20,
  130. }}>
  131. <Card.Content>
  132. <Headline
  133. style={{
  134. textAlign: 'center',
  135. color: this.isHighScore
  136. ? props.theme.colors.gameGold
  137. : props.theme.colors.primary,
  138. }}>
  139. {this.isHighScore
  140. ? i18n.t('screens.game.newHighScore')
  141. : i18n.t('screens.game.gameOver')}
  142. </Headline>
  143. <Divider />
  144. <View
  145. style={{
  146. flexDirection: 'row',
  147. marginLeft: 'auto',
  148. marginRight: 'auto',
  149. marginTop: 10,
  150. marginBottom: 10,
  151. }}>
  152. <Text
  153. style={{
  154. fontSize: 20,
  155. }}>
  156. {i18n.t('screens.game.score', {score: stats.score})}
  157. </Text>
  158. <MaterialCommunityIcons
  159. name="star"
  160. color={props.theme.colors.tetrisScore}
  161. size={30}
  162. style={{
  163. marginLeft: 5,
  164. }}
  165. />
  166. </View>
  167. <View
  168. style={{
  169. flexDirection: 'row',
  170. marginLeft: 'auto',
  171. marginRight: 'auto',
  172. }}>
  173. <Text>{i18n.t('screens.game.level')}</Text>
  174. <MaterialCommunityIcons
  175. style={{
  176. marginRight: 5,
  177. marginLeft: 5,
  178. }}
  179. name="gamepad-square"
  180. size={20}
  181. color={props.theme.colors.textDisabled}
  182. />
  183. <Text>{stats.level}</Text>
  184. </View>
  185. <View
  186. style={{
  187. flexDirection: 'row',
  188. marginLeft: 'auto',
  189. marginRight: 'auto',
  190. }}>
  191. <Text>{i18n.t('screens.game.time')}</Text>
  192. <MaterialCommunityIcons
  193. style={{
  194. marginRight: 5,
  195. marginLeft: 5,
  196. }}
  197. name="timer"
  198. size={20}
  199. color={props.theme.colors.textDisabled}
  200. />
  201. <Text>{stats.time}</Text>
  202. </View>
  203. </Card.Content>
  204. </Card>
  205. </View>
  206. );
  207. }
  208. getWelcomeText(): React.Node {
  209. const {props} = this;
  210. return (
  211. <View>
  212. <Mascot
  213. emotion={MASCOT_STYLE.COOL}
  214. style={{
  215. width: '40%',
  216. marginLeft: 'auto',
  217. marginRight: 'auto',
  218. }}
  219. />
  220. <SpeechArrow
  221. style={{marginLeft: '60%'}}
  222. size={20}
  223. color={props.theme.colors.mascotMessageArrow}
  224. />
  225. <Card
  226. style={{
  227. borderColor: props.theme.colors.mascotMessageArrow,
  228. borderWidth: 2,
  229. marginLeft: 10,
  230. marginRight: 10,
  231. }}>
  232. <Card.Content>
  233. <Headline
  234. style={{
  235. textAlign: 'center',
  236. color: props.theme.colors.primary,
  237. }}>
  238. {i18n.t('screens.game.welcomeTitle')}
  239. </Headline>
  240. <Divider />
  241. <Paragraph
  242. style={{
  243. textAlign: 'center',
  244. marginTop: 10,
  245. }}>
  246. {i18n.t('screens.game.welcomeMessage')}
  247. </Paragraph>
  248. </Card.Content>
  249. </Card>
  250. </View>
  251. );
  252. }
  253. getPodiumRender(place: 1 | 2 | 3, score: string): React.Node {
  254. const {props} = this;
  255. let icon = 'podium-gold';
  256. let color = props.theme.colors.gameGold;
  257. let fontSize = 20;
  258. let size = 70;
  259. if (place === 2) {
  260. icon = 'podium-silver';
  261. color = props.theme.colors.gameSilver;
  262. fontSize = 18;
  263. size = 60;
  264. } else if (place === 3) {
  265. icon = 'podium-bronze';
  266. color = props.theme.colors.gameBronze;
  267. fontSize = 15;
  268. size = 50;
  269. }
  270. return (
  271. <View
  272. style={{
  273. marginLeft: place === 2 ? 20 : 'auto',
  274. marginRight: place === 3 ? 20 : 'auto',
  275. flexDirection: 'column',
  276. alignItems: 'center',
  277. justifyContent: 'flex-end',
  278. }}>
  279. {this.isHighScore && place === 1 ? (
  280. <Animatable.View
  281. animation="swing"
  282. iterationCount="infinite"
  283. duration={2000}
  284. delay={1000}
  285. useNativeDriver
  286. style={{
  287. position: 'absolute',
  288. top: -20,
  289. }}>
  290. <Animatable.View
  291. animation="pulse"
  292. iterationCount="infinite"
  293. useNativeDriver>
  294. <MaterialCommunityIcons
  295. name="decagram"
  296. color={props.theme.colors.gameGold}
  297. size={150}
  298. />
  299. </Animatable.View>
  300. </Animatable.View>
  301. ) : null}
  302. <MaterialCommunityIcons
  303. name={icon}
  304. color={this.isHighScore && place === 1 ? '#fff' : color}
  305. size={size}
  306. />
  307. <Text
  308. style={{
  309. textAlign: 'center',
  310. fontWeight: place === 1 ? 'bold' : null,
  311. fontSize,
  312. }}>
  313. {score}
  314. </Text>
  315. </View>
  316. );
  317. }
  318. getTopScoresRender(): React.Node {
  319. const gold = this.scores.length > 0 ? this.scores[0] : '-';
  320. const silver = this.scores.length > 1 ? this.scores[1] : '-';
  321. const bronze = this.scores.length > 2 ? this.scores[2] : '-';
  322. return (
  323. <View
  324. style={{
  325. marginBottom: 20,
  326. marginTop: 20,
  327. }}>
  328. {this.getPodiumRender(1, gold.toString())}
  329. <View
  330. style={{
  331. flexDirection: 'row',
  332. marginLeft: 'auto',
  333. marginRight: 'auto',
  334. }}>
  335. {this.getPodiumRender(3, bronze.toString())}
  336. {this.getPodiumRender(2, silver.toString())}
  337. </View>
  338. </View>
  339. );
  340. }
  341. getMainContent(): React.Node {
  342. const {props} = this;
  343. return (
  344. <View style={{flex: 1}}>
  345. {this.gameStats != null
  346. ? this.getPostGameContent(this.gameStats)
  347. : this.getWelcomeText()}
  348. <Button
  349. icon="play"
  350. mode="contained"
  351. onPress={() => {
  352. props.navigation.replace('game-main', {
  353. highScore: this.scores.length > 0 ? this.scores[0] : null,
  354. });
  355. }}
  356. style={{
  357. marginLeft: 'auto',
  358. marginRight: 'auto',
  359. marginTop: 10,
  360. }}>
  361. {i18n.t('screens.game.play')}
  362. </Button>
  363. {this.getTopScoresRender()}
  364. </View>
  365. );
  366. }
  367. keyExtractor = (item: number): string => item.toString();
  368. recoverGameScore() {
  369. const {route} = this.props;
  370. this.gameStats = route.params;
  371. if (this.gameStats.score != null) {
  372. this.isHighScore =
  373. this.scores.length === 0 || this.gameStats.score > this.scores[0];
  374. for (let i = 0; i < 3; i += 1) {
  375. if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
  376. this.scores.splice(i, 0, this.gameStats.score);
  377. break;
  378. } else if (this.scores.length <= i) {
  379. this.scores.push(this.gameStats.score);
  380. break;
  381. }
  382. }
  383. if (this.scores.length > 3) this.scores.splice(3, 1);
  384. AsyncStorageManager.set(
  385. AsyncStorageManager.PREFERENCES.gameScores.key,
  386. this.scores,
  387. );
  388. }
  389. }
  390. render(): React.Node {
  391. const {props} = this;
  392. return (
  393. <View style={{flex: 1}}>
  394. {this.getPiecesBackground()}
  395. <LinearGradient
  396. style={{flex: 1}}
  397. colors={[
  398. `${props.theme.colors.background}00`,
  399. props.theme.colors.background,
  400. ]}
  401. start={{x: 0, y: 0}}
  402. end={{x: 0, y: 1}}>
  403. <CollapsibleScrollView>
  404. {this.getMainContent()}
  405. <MascotPopup
  406. prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
  407. title={i18n.t('screens.game.mascotDialog.title')}
  408. message={i18n.t('screens.game.mascotDialog.message')}
  409. icon="gamepad-variant"
  410. buttons={{
  411. action: null,
  412. cancel: {
  413. message: i18n.t('screens.game.mascotDialog.button'),
  414. icon: 'check',
  415. },
  416. }}
  417. emotion={MASCOT_STYLE.COOL}
  418. />
  419. </CollapsibleScrollView>
  420. </LinearGradient>
  421. </View>
  422. );
  423. }
  424. }
  425. export default withTheme(GameStartScreen);