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

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