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

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