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.

GameStartScreen.js 16KB

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