From fde9a12ef963f577c5af836d7ae4196f9193b4fc Mon Sep 17 00:00:00 2001 From: Arnaud Vergnet Date: Tue, 22 Sep 2020 23:13:09 +0200 Subject: [PATCH] Update game to use TypeScript --- .../Shapes/{BaseShape.js => BaseShape.ts} | 33 ++-- .../Game/Shapes/{ShapeI.js => ShapeI.ts} | 6 +- .../Game/Shapes/{ShapeJ.js => ShapeJ.ts} | 6 +- .../Game/Shapes/{ShapeL.js => ShapeL.ts} | 6 +- .../Game/Shapes/{ShapeO.js => ShapeO.ts} | 6 +- .../Game/Shapes/{ShapeS.js => ShapeS.ts} | 6 +- .../Game/Shapes/{ShapeT.js => ShapeT.ts} | 6 +- .../Game/Shapes/{ShapeZ.js => ShapeZ.ts} | 6 +- .../{CellComponent.js => CellComponent.tsx} | 40 ++--- src/screens/Game/components/GridComponent.js | 76 --------- src/screens/Game/components/GridComponent.tsx | 68 ++++++++ .../components/{Preview.js => Preview.tsx} | 65 ++++---- .../Game/logic/{GameLogic.js => GameLogic.ts} | 148 +++++++++++------- .../logic/{GridManager.js => GridManager.ts} | 11 +- src/screens/Game/logic/{Piece.js => Piece.ts} | 39 +++-- .../{ScoreManager.js => ScoreManager.ts} | 6 +- .../{GameMainScreen.js => GameMainScreen.tsx} | 60 +++---- ...GameStartScreen.js => GameStartScreen.tsx} | 51 +++--- 18 files changed, 326 insertions(+), 313 deletions(-) rename src/screens/Game/Shapes/{BaseShape.js => BaseShape.ts} (85%) rename src/screens/Game/Shapes/{ShapeI.js => ShapeI.ts} (89%) rename src/screens/Game/Shapes/{ShapeJ.js => ShapeJ.ts} (88%) rename src/screens/Game/Shapes/{ShapeL.js => ShapeL.ts} (88%) rename src/screens/Game/Shapes/{ShapeO.js => ShapeO.ts} (87%) rename src/screens/Game/Shapes/{ShapeS.js => ShapeS.ts} (88%) rename src/screens/Game/Shapes/{ShapeT.js => ShapeT.ts} (88%) rename src/screens/Game/Shapes/{ShapeZ.js => ShapeZ.ts} (88%) rename src/screens/Game/components/{CellComponent.js => CellComponent.tsx} (58%) delete mode 100644 src/screens/Game/components/GridComponent.js create mode 100644 src/screens/Game/components/GridComponent.tsx rename src/screens/Game/components/{Preview.js => Preview.tsx} (53%) rename src/screens/Game/logic/{GameLogic.js => GameLogic.ts} (72%) rename src/screens/Game/logic/{GridManager.js => GridManager.ts} (93%) rename src/screens/Game/logic/{Piece.js => Piece.ts} (89%) rename src/screens/Game/logic/{ScoreManager.js => ScoreManager.ts} (97%) rename src/screens/Game/screens/{GameMainScreen.js => GameMainScreen.tsx} (93%) rename src/screens/Game/screens/{GameStartScreen.js => GameStartScreen.tsx} (93%) diff --git a/src/screens/Game/Shapes/BaseShape.js b/src/screens/Game/Shapes/BaseShape.ts similarity index 85% rename from src/screens/Game/Shapes/BaseShape.js rename to src/screens/Game/Shapes/BaseShape.ts index e113b03..86a95fe 100644 --- a/src/screens/Game/Shapes/BaseShape.js +++ b/src/screens/Game/Shapes/BaseShape.ts @@ -19,11 +19,9 @@ // @flow -import type {CustomThemeType} from '../../../managers/ThemeManager'; - export type CoordinatesType = { - x: number, - y: number, + x: number; + y: number; }; export type ShapeType = Array>; @@ -40,14 +38,15 @@ export default class BaseShape { position: CoordinatesType; - theme: CustomThemeType; + theme: ReactNativePaper.Theme; /** * Prevent instantiation if classname is BaseShape to force class to be abstract */ - constructor(theme: CustomThemeType) { - if (this.constructor === BaseShape) + constructor(theme: ReactNativePaper.Theme) { + if (this.constructor === BaseShape) { throw new Error("Abstract class can't be instantiated"); + } this.theme = theme; this.#rotation = 0; this.position = {x: 0, y: 0}; @@ -58,7 +57,6 @@ export default class BaseShape { * Gets this shape's color. * Must be implemented by child class */ - // eslint-disable-next-line class-methods-use-this getColor(): string { throw new Error("Method 'getColor()' must be implemented"); } @@ -69,7 +67,6 @@ export default class BaseShape { * * Used by tests to read private fields */ - // eslint-disable-next-line class-methods-use-this getShapes(): Array { throw new Error("Method 'getShapes()' must be implemented"); } @@ -98,7 +95,9 @@ export default class BaseShape { x: this.position.x + col, y: this.position.y + row, }); - } else coordinates.push({x: col, y: row}); + } else { + coordinates.push({x: col, y: row}); + } } } } @@ -111,10 +110,16 @@ export default class BaseShape { * @param isForward Should we rotate clockwise? */ rotate(isForward: boolean) { - if (isForward) this.#rotation += 1; - else this.#rotation -= 1; - if (this.#rotation > 3) this.#rotation = 0; - else if (this.#rotation < 0) this.#rotation = 3; + if (isForward) { + this.#rotation += 1; + } else { + this.#rotation -= 1; + } + if (this.#rotation > 3) { + this.#rotation = 0; + } else if (this.#rotation < 0) { + this.#rotation = 3; + } this.#currentShape = this.getShapes()[this.#rotation]; } diff --git a/src/screens/Game/Shapes/ShapeI.js b/src/screens/Game/Shapes/ShapeI.ts similarity index 89% rename from src/screens/Game/Shapes/ShapeI.js rename to src/screens/Game/Shapes/ShapeI.ts index 47688bd..d8b395f 100644 --- a/src/screens/Game/Shapes/ShapeI.js +++ b/src/screens/Game/Shapes/ShapeI.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeI extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeI extends BaseShape { return this.theme.colors.tetrisI; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeJ.js b/src/screens/Game/Shapes/ShapeJ.ts similarity index 88% rename from src/screens/Game/Shapes/ShapeJ.js rename to src/screens/Game/Shapes/ShapeJ.ts index 3d9cb3f..09c6bfd 100644 --- a/src/screens/Game/Shapes/ShapeJ.js +++ b/src/screens/Game/Shapes/ShapeJ.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeJ extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeJ extends BaseShape { return this.theme.colors.tetrisJ; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeL.js b/src/screens/Game/Shapes/ShapeL.ts similarity index 88% rename from src/screens/Game/Shapes/ShapeL.js rename to src/screens/Game/Shapes/ShapeL.ts index fc37de3..310f7c2 100644 --- a/src/screens/Game/Shapes/ShapeL.js +++ b/src/screens/Game/Shapes/ShapeL.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeL extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeL extends BaseShape { return this.theme.colors.tetrisL; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeO.js b/src/screens/Game/Shapes/ShapeO.ts similarity index 87% rename from src/screens/Game/Shapes/ShapeO.js rename to src/screens/Game/Shapes/ShapeO.ts index 8551a46..00eec59 100644 --- a/src/screens/Game/Shapes/ShapeO.js +++ b/src/screens/Game/Shapes/ShapeO.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeO extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 4; } @@ -33,7 +30,6 @@ export default class ShapeO extends BaseShape { return this.theme.colors.tetrisO; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeS.js b/src/screens/Game/Shapes/ShapeS.ts similarity index 88% rename from src/screens/Game/Shapes/ShapeS.js rename to src/screens/Game/Shapes/ShapeS.ts index ac4b161..6f397df 100644 --- a/src/screens/Game/Shapes/ShapeS.js +++ b/src/screens/Game/Shapes/ShapeS.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeS extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeS extends BaseShape { return this.theme.colors.tetrisS; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeT.js b/src/screens/Game/Shapes/ShapeT.ts similarity index 88% rename from src/screens/Game/Shapes/ShapeT.js rename to src/screens/Game/Shapes/ShapeT.ts index b011ed6..71b1e80 100644 --- a/src/screens/Game/Shapes/ShapeT.js +++ b/src/screens/Game/Shapes/ShapeT.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeT extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeT extends BaseShape { return this.theme.colors.tetrisT; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/Shapes/ShapeZ.js b/src/screens/Game/Shapes/ShapeZ.ts similarity index 88% rename from src/screens/Game/Shapes/ShapeZ.js rename to src/screens/Game/Shapes/ShapeZ.ts index 94a33b1..b24e13e 100644 --- a/src/screens/Game/Shapes/ShapeZ.js +++ b/src/screens/Game/Shapes/ShapeZ.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import BaseShape from './BaseShape'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {ShapeType} from './BaseShape'; export default class ShapeZ extends BaseShape { - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { super(theme); this.position.x = 3; } @@ -33,7 +30,6 @@ export default class ShapeZ extends BaseShape { return this.theme.colors.tetrisZ; } - // eslint-disable-next-line class-methods-use-this getShapes(): Array { return [ [ diff --git a/src/screens/Game/components/CellComponent.js b/src/screens/Game/components/CellComponent.tsx similarity index 58% rename from src/screens/Game/components/CellComponent.js rename to src/screens/Game/components/CellComponent.tsx index d19fa65..bf266a6 100644 --- a/src/screens/Game/components/CellComponent.js +++ b/src/screens/Game/components/CellComponent.tsx @@ -17,35 +17,29 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {View} from 'react-native'; -import {withTheme} from 'react-native-paper'; -export type CellType = {color: string, isEmpty: boolean, key: string}; +export type CellType = {color: string; isEmpty: boolean; key: string}; type PropsType = { - cell: CellType, + cell: CellType; }; -class CellComponent extends React.PureComponent { - render(): React.Node { - const {props} = this; - const item = props.cell; - return ( - - ); - } +function CellComponent(props: PropsType) { + const item = props.cell; + return ( + + ); } -export default withTheme(CellComponent); +export default CellComponent; diff --git a/src/screens/Game/components/GridComponent.js b/src/screens/Game/components/GridComponent.js deleted file mode 100644 index 17c6846..0000000 --- a/src/screens/Game/components/GridComponent.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2019 - 2020 Arnaud Vergnet. - * - * This file is part of Campus INSAT. - * - * Campus INSAT is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Campus INSAT is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Campus INSAT. If not, see . - */ - -// @flow - -import * as React from 'react'; -import {View} from 'react-native'; -import {withTheme} from 'react-native-paper'; -import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet'; -import type {CellType} from './CellComponent'; -import CellComponent from './CellComponent'; - -export type GridType = Array>; - -type PropsType = { - grid: Array>, - height: number, - width: number, - style: ViewStyle, -}; - -class GridComponent extends React.Component { - getRow(rowNumber: number): React.Node { - const {grid} = this.props; - return ( - - {grid[rowNumber].map(this.getCellRender)} - - ); - } - - getCellRender = (item: CellType): React.Node => { - return ; - }; - - getGrid(): React.Node { - const {height} = this.props; - const rows = []; - for (let i = 0; i < height; i += 1) { - rows.push(this.getRow(i)); - } - return rows; - } - - render(): React.Node { - const {style, width, height} = this.props; - return ( - - {this.getGrid()} - - ); - } -} - -export default withTheme(GridComponent); diff --git a/src/screens/Game/components/GridComponent.tsx b/src/screens/Game/components/GridComponent.tsx new file mode 100644 index 0000000..05706b2 --- /dev/null +++ b/src/screens/Game/components/GridComponent.tsx @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 - 2020 Arnaud Vergnet. + * + * This file is part of Campus INSAT. + * + * Campus INSAT is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Campus INSAT is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Campus INSAT. If not, see . + */ + +import * as React from 'react'; +import {View, ViewStyle} from 'react-native'; +import type {CellType} from './CellComponent'; +import CellComponent from './CellComponent'; + +export type GridType = Array>; + +type PropsType = { + grid: GridType; + height: number; + width: number; + style: ViewStyle; +}; + +const getCellRender = (item: CellType) => { + return ; +}; + +function getRow(grid: GridType, rowNumber: number) { + return ( + + {grid[rowNumber].map(getCellRender)} + + ); +} + +function getGrid(grid: GridType, height: number) { + const rows = []; + for (let i = 0; i < height; i += 1) { + rows.push(getRow(grid, i)); + } + return rows; +} + +function GridComponent(props: PropsType) { + const {style, width, height, grid} = props; + return ( + + {getGrid(grid, height)} + + ); +} + +export default GridComponent; diff --git a/src/screens/Game/components/Preview.js b/src/screens/Game/components/Preview.tsx similarity index 53% rename from src/screens/Game/components/Preview.js rename to src/screens/Game/components/Preview.tsx index 1bf2e94..58dca1a 100644 --- a/src/screens/Game/components/Preview.js +++ b/src/screens/Game/components/Preview.tsx @@ -17,53 +17,48 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; -import {View} from 'react-native'; -import {withTheme} from 'react-native-paper'; -import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet'; +import {View, ViewStyle} from 'react-native'; import type {GridType} from './GridComponent'; import GridComponent from './GridComponent'; type PropsType = { - items: Array, - style: ViewStyle, + items: Array; + style: ViewStyle; }; +function getGridRender(item: GridType, index: number) { + return ( + + ); +} + +function getGrids(items: Array) { + const grids: Array = []; + items.forEach((item: GridType, index: number) => { + grids.push(getGridRender(item, index)); + }); + return grids; +} + class Preview extends React.PureComponent { - getGrids(): React.Node { - const {items} = this.props; - const grids = []; - items.forEach((item: GridType, index: number) => { - grids.push(Preview.getGridRender(item, index)); - }); - return grids; - } - - static getGridRender(item: GridType, index: number): React.Node { - return ( - - ); - } - - render(): React.Node { + render() { const {style, items} = this.props; if (items.length > 0) { - return {this.getGrids()}; + return {getGrids(items)}; } return null; } } -export default withTheme(Preview); +export default Preview; diff --git a/src/screens/Game/logic/GameLogic.js b/src/screens/Game/logic/GameLogic.ts similarity index 72% rename from src/screens/Game/logic/GameLogic.js rename to src/screens/Game/logic/GameLogic.ts index 968ffb6..4458a02 100644 --- a/src/screens/Game/logic/GameLogic.js +++ b/src/screens/Game/logic/GameLogic.ts @@ -17,12 +17,9 @@ * along with Campus INSAT. If not, see . */ -// @flow - import Piece from './Piece'; import ScoreManager from './ScoreManager'; import GridManager from './GridManager'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {GridType} from '../components/GridComponent'; export type TickCallbackType = ( @@ -58,15 +55,15 @@ export default class GameLogic { gameTime: number; - currentObject: Piece; + currentObject?: Piece; gameTick: number; - gameTickInterval: IntervalID; + gameTickInterval?: NodeJS.Timeout; - gameTimeInterval: IntervalID; + gameTimeInterval?: NodeJS.Timeout; - pressInInterval: TimeoutID; + pressInInterval?: NodeJS.Timeout; isPressedIn: boolean; @@ -78,15 +75,19 @@ export default class GameLogic { nextPiecesCount: number; - tickCallback: TickCallbackType; + tickCallback?: TickCallbackType; - clockCallback: ClockCallbackType; + clockCallback?: ClockCallbackType; - endCallback: EndCallbackType; + endCallback?: EndCallbackType; - theme: CustomThemeType; + theme: ReactNativePaper.Theme; + + constructor(height: number, width: number, theme: ReactNativePaper.Theme) { + this.gameTime = 0; + this.gameTick = 0; + this.isPressedIn = false; - constructor(height: number, width: number, theme: CustomThemeType) { this.height = height; this.width = width; this.gameRunning = false; @@ -121,12 +122,16 @@ export default class GameLogic { } onFreeze = () => { - this.gridManager.freezeTetromino(this.currentObject, this.scoreManager); + if (this.currentObject) { + this.gridManager.freezeTetromino(this.currentObject, this.scoreManager); + } this.createTetromino(); }; setNewGameTick(level: number) { - if (level >= GameLogic.levelTicks.length) return; + if (level >= GameLogic.levelTicks.length) { + return; + } this.gameTick = GameLogic.levelTicks[level]; this.stopTick(); this.startTick(); @@ -145,11 +150,15 @@ export default class GameLogic { } stopClock() { - clearInterval(this.gameTimeInterval); + if (this.gameTimeInterval) { + clearInterval(this.gameTimeInterval); + } } stopTick() { - clearInterval(this.gameTickInterval); + if (this.gameTickInterval) { + clearInterval(this.gameTickInterval); + } } stopGameTime() { @@ -162,27 +171,34 @@ export default class GameLogic { this.startTick(); } - onTick(callback: TickCallbackType) { - this.currentObject.tryMove( - 0, - 1, - this.gridManager.getCurrentGrid(), - this.getWidth(), - this.getHeight(), - this.onFreeze, - ); - callback( - this.scoreManager.getScore(), - this.scoreManager.getLevel(), - this.gridManager.getCurrentGrid(), - ); - if (this.scoreManager.canLevelUp()) + onTick(callback?: TickCallbackType) { + if (this.currentObject) { + this.currentObject.tryMove( + 0, + 1, + this.gridManager.getCurrentGrid(), + this.getWidth(), + this.getHeight(), + this.onFreeze, + ); + } + if (callback) { + callback( + this.scoreManager.getScore(), + this.scoreManager.getLevel(), + this.gridManager.getCurrentGrid(), + ); + } + if (this.scoreManager.canLevelUp()) { this.setNewGameTick(this.scoreManager.getLevel()); + } } - onClock(callback: ClockCallbackType) { + onClock(callback?: ClockCallbackType) { this.gameTime += 1; - callback(this.gameTime); + if (callback) { + callback(this.gameTime); + } } canUseInput(): boolean { @@ -210,15 +226,19 @@ export default class GameLogic { x: number, y: number, ) { - if (!this.canUseInput() || !this.isPressedIn) return; - const moved = this.currentObject.tryMove( - x, - y, - this.gridManager.getCurrentGrid(), - this.getWidth(), - this.getHeight(), - this.onFreeze, - ); + if (!this.canUseInput() || !this.isPressedIn) { + return; + } + const moved = + this.currentObject && + this.currentObject.tryMove( + x, + y, + this.gridManager.getCurrentGrid(), + this.getWidth(), + this.getHeight(), + this.onFreeze, + ); if (moved) { if (y === 1) { this.scoreManager.incrementScore(); @@ -226,7 +246,9 @@ export default class GameLogic { this.gridManager.getCurrentGrid(), this.scoreManager.getScore(), ); - } else callback(this.gridManager.getCurrentGrid()); + } else { + callback(this.gridManager.getCurrentGrid()); + } } this.pressInInterval = setTimeout( () => { @@ -238,20 +260,26 @@ export default class GameLogic { pressedOut() { this.isPressedIn = false; - clearTimeout(this.pressInInterval); + if (this.pressInInterval) { + clearTimeout(this.pressInInterval); + } } rotatePressed(callback: MovementCallbackType) { - if (!this.canUseInput()) return; + if (!this.canUseInput()) { + return; + } if ( + this.currentObject && this.currentObject.tryRotate( this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(), ) - ) + ) { callback(this.gridManager.getCurrentGrid()); + } } getNextPiecesPreviews(): Array { @@ -266,7 +294,10 @@ export default class GameLogic { } recoverNextPiece() { - this.currentObject = this.nextPieces.shift(); + const next = this.nextPieces.shift(); + if (next) { + this.currentObject = next; + } this.generateNextPieces(); } @@ -280,27 +311,36 @@ export default class GameLogic { this.pressedOut(); this.recoverNextPiece(); if ( + this.currentObject && !this.currentObject.isPositionValid( this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(), ) - ) + ) { this.endGame(false); + } } togglePause() { - if (!this.gameRunning) return; + if (!this.gameRunning) { + return; + } this.gamePaused = !this.gamePaused; - if (this.gamePaused) this.stopGameTime(); - else this.startGameTime(); + if (this.gamePaused) { + this.stopGameTime(); + } else { + this.startGameTime(); + } } endGame(isRestart: boolean) { this.gameRunning = false; this.gamePaused = false; this.stopGameTime(); - this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart); + if (this.endCallback) { + this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart); + } } startGame( @@ -308,7 +348,9 @@ export default class GameLogic { clockCallback: ClockCallbackType, endCallback: EndCallbackType, ) { - if (this.gameRunning) this.endGame(true); + if (this.gameRunning) { + this.endGame(true); + } this.gameRunning = true; this.gamePaused = false; this.gameTime = 0; diff --git a/src/screens/Game/logic/GridManager.js b/src/screens/Game/logic/GridManager.ts similarity index 93% rename from src/screens/Game/logic/GridManager.js rename to src/screens/Game/logic/GridManager.ts index 2d6628b..f242151 100644 --- a/src/screens/Game/logic/GridManager.js +++ b/src/screens/Game/logic/GridManager.ts @@ -17,14 +17,11 @@ * along with Campus INSAT. If not, see . */ -// @flow - import Piece from './Piece'; import ScoreManager from './ScoreManager'; import type {CoordinatesType} from '../Shapes/BaseShape'; import type {GridType} from '../components/GridComponent'; import type {CellType} from '../components/CellComponent'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; /** * Class used to manage the game grid @@ -32,7 +29,7 @@ import type {CustomThemeType} from '../../../managers/ThemeManager'; export default class GridManager { #currentGrid: GridType; - #theme: CustomThemeType; + #theme: ReactNativePaper.Theme; /** * Initializes a grid of the given size @@ -41,7 +38,7 @@ export default class GridManager { * @param height The grid height * @param theme Object containing current theme */ - constructor(width: number, height: number, theme: CustomThemeType) { + constructor(width: number, height: number, theme: ReactNativePaper.Theme) { this.#theme = theme; this.#currentGrid = this.getEmptyGrid(height, width); } @@ -121,7 +118,9 @@ export default class GridManager { break; } } - if (isLineFull && rows.indexOf(pos[i].y) === -1) rows.push(pos[i].y); + if (isLineFull && rows.indexOf(pos[i].y) === -1) { + rows.push(pos[i].y); + } } return rows; } diff --git a/src/screens/Game/logic/Piece.js b/src/screens/Game/logic/Piece.ts similarity index 89% rename from src/screens/Game/logic/Piece.js rename to src/screens/Game/logic/Piece.ts index f06e3e0..d2fc852 100644 --- a/src/screens/Game/logic/Piece.js +++ b/src/screens/Game/logic/Piece.ts @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import ShapeL from '../Shapes/ShapeL'; import ShapeI from '../Shapes/ShapeI'; import ShapeJ from '../Shapes/ShapeJ'; @@ -29,7 +27,6 @@ import ShapeZ from '../Shapes/ShapeZ'; import type {CoordinatesType} from '../Shapes/BaseShape'; import BaseShape from '../Shapes/BaseShape'; import type {GridType} from '../components/GridComponent'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; /** * Class used as an abstraction layer for shapes. @@ -41,14 +38,14 @@ export default class Piece { currentShape: BaseShape; - theme: CustomThemeType; + theme: ReactNativePaper.Theme; /** * Initializes this piece's color and shape * * @param theme Object containing current theme */ - constructor(theme: CustomThemeType) { + constructor(theme: ReactNativePaper.Theme) { this.currentShape = this.getRandomShape(theme); this.theme = theme; } @@ -58,7 +55,7 @@ export default class Piece { * * @param theme Object containing current theme */ - getRandomShape(theme: CustomThemeType): BaseShape { + getRandomShape(theme: ReactNativePaper.Theme): BaseShape { return new this.shapes[Math.floor(Math.random() * 7)](theme); } @@ -72,7 +69,6 @@ export default class Piece { true, ); pos.forEach((coordinates: CoordinatesType) => { - // eslint-disable-next-line no-param-reassign grid[coordinates.y][coordinates.x] = { color: this.theme.colors.tetrisBackground, isEmpty: true, @@ -92,7 +88,6 @@ export default class Piece { !isPreview, ); pos.forEach((coordinates: CoordinatesType) => { - // eslint-disable-next-line no-param-reassign grid[coordinates.y][coordinates.x] = { color: this.currentShape.getColor(), isEmpty: false, @@ -150,21 +145,35 @@ export default class Piece { ): boolean { let newX = x; let newY = y; - if (x > 1) newX = 1; // Prevent moving from more than one tile - if (x < -1) newX = -1; - if (y > 1) newY = 1; - if (y < -1) newY = -1; - if (x !== 0 && y !== 0) newY = 0; // Prevent diagonal movement + if (x > 1) { + newX = 1; + } // Prevent moving from more than one tile + if (x < -1) { + newX = -1; + } + if (y > 1) { + newY = 1; + } + if (y < -1) { + newY = -1; + } + if (x !== 0 && y !== 0) { + newY = 0; + } // Prevent diagonal movement this.removeFromGrid(grid); this.currentShape.move(newX, newY); const isValid = this.isPositionValid(grid, width, height); - if (!isValid) this.currentShape.move(-newX, -newY); + if (!isValid) { + this.currentShape.move(-newX, -newY); + } const shouldFreeze = !isValid && newY !== 0; this.toGrid(grid, false); - if (shouldFreeze) freezeCallback(); + if (shouldFreeze) { + freezeCallback(); + } return isValid; } diff --git a/src/screens/Game/logic/ScoreManager.js b/src/screens/Game/logic/ScoreManager.ts similarity index 97% rename from src/screens/Game/logic/ScoreManager.js rename to src/screens/Game/logic/ScoreManager.ts index df8a666..8b3a0df 100644 --- a/src/screens/Game/logic/ScoreManager.js +++ b/src/screens/Game/logic/ScoreManager.ts @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - /** * Class used to manage game score */ @@ -83,7 +81,9 @@ export default class ScoreManager { * @param numberRemoved The number of lines removed at the same time */ addLinesRemovedPoints(numberRemoved: number) { - if (numberRemoved < 1 || numberRemoved > 4) return; + if (numberRemoved < 1 || numberRemoved > 4) { + return; + } this.#score += this.#scoreLinesModifier[numberRemoved - 1] * (this.#level + 1); switch (numberRemoved) { diff --git a/src/screens/Game/screens/GameMainScreen.js b/src/screens/Game/screens/GameMainScreen.tsx similarity index 93% rename from src/screens/Game/screens/GameMainScreen.js rename to src/screens/Game/screens/GameMainScreen.tsx index 141577b..89356b1 100644 --- a/src/screens/Game/screens/GameMainScreen.js +++ b/src/screens/Game/screens/GameMainScreen.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {View} from 'react-native'; import {Caption, IconButton, Text, withTheme} from 'react-native-paper'; @@ -32,27 +30,26 @@ import Preview from '../components/Preview'; import MaterialHeaderButtons, { Item, } from '../../../components/Overrides/CustomHeaderButton'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog'; import OptionsDialog from '../../../components/Dialogs/OptionsDialog'; type PropsType = { - navigation: StackNavigationProp, - route: {params: {highScore: number}}, - theme: CustomThemeType, + navigation: StackNavigationProp; + route: {params: {highScore: number}}; + theme: ReactNativePaper.Theme; }; type StateType = { - grid: GridType, - gameTime: number, - gameScore: number, - gameLevel: number, + grid: GridType; + gameTime: number; + gameScore: number; + gameLevel: number; - dialogVisible: boolean, - dialogTitle: string, - dialogMessage: string, - dialogButtons: Array, - onDialogDismiss: () => void, + dialogVisible: boolean; + dialogTitle: string; + dialogMessage: string; + dialogButtons: Array; + onDialogDismiss: () => void; }; class GameMainScreen extends React.Component { @@ -62,11 +59,13 @@ class GameMainScreen extends React.Component { date.setMinutes(0); date.setSeconds(seconds); let format; - if (date.getHours()) + if (date.getHours()) { format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; - else if (date.getMinutes()) + } else if (date.getMinutes()) { format = `${date.getMinutes()}:${date.getSeconds()}`; - else format = date.getSeconds().toString(); + } else { + format = date.getSeconds().toString(); + } return format; } @@ -76,6 +75,7 @@ class GameMainScreen extends React.Component { constructor(props: PropsType) { super(props); + this.highScore = null; this.logic = new GameLogic(20, 10, props.theme); this.state = { grid: this.logic.getCurrentGrid(), @@ -88,8 +88,9 @@ class GameMainScreen extends React.Component { dialogButtons: [], onDialogDismiss: () => {}, }; - if (props.route.params != null) + if (props.route.params != null) { this.highScore = props.route.params.highScore; + } } componentDidMount() { @@ -104,7 +105,7 @@ class GameMainScreen extends React.Component { this.logic.endGame(true); } - getRightButton = (): React.Node => { + getRightButton = () => { return ( @@ -136,15 +137,16 @@ class GameMainScreen extends React.Component { gameTime: time, gameScore: score, }); - if (!isRestart) + if (!isRestart) { props.navigation.replace('game-start', { score: state.gameScore, level: state.gameLevel, time: state.gameTime, }); + } }; - getStatusIcons(): React.Node { + getStatusIcons() { const {props, state} = this; return ( { ); } - getScoreIcon(): React.Node { + getScoreIcon() { const {props, state} = this; const highScore = this.highScore == null || state.gameScore > this.highScore @@ -285,7 +287,7 @@ class GameMainScreen extends React.Component { ); } - getControlButtons(): React.Node { + getControlButtons() { const {props} = this; return ( { updateGridScore = (newGrid: GridType, score?: number) => { this.setState((prevState: StateType): { - grid: GridType, - gameScore: number, + grid: GridType; + gameScore: number; } => ({ grid: newGrid, gameScore: score != null ? score : prevState.gameScore, @@ -363,7 +365,9 @@ class GameMainScreen extends React.Component { togglePause = () => { this.logic.togglePause(); - if (this.logic.isGamePaused()) this.showPausePopup(); + if (this.logic.isGamePaused()) { + this.showPausePopup(); + } }; showPausePopup = () => { @@ -415,7 +419,7 @@ class GameMainScreen extends React.Component { this.logic.startGame(this.onTick, this.onClock, this.onGameEnd); }; - render(): React.Node { + render() { const {props, state} = this; return ( diff --git a/src/screens/Game/screens/GameStartScreen.js b/src/screens/Game/screens/GameStartScreen.tsx similarity index 93% rename from src/screens/Game/screens/GameStartScreen.js rename to src/screens/Game/screens/GameStartScreen.tsx index c6c5543..0248f08 100644 --- a/src/screens/Game/screens/GameStartScreen.js +++ b/src/screens/Game/screens/GameStartScreen.tsx @@ -17,8 +17,6 @@ * along with Campus INSAT. If not, see . */ -// @flow - import * as React from 'react'; import {StackNavigationProp} from '@react-navigation/stack'; import { @@ -35,7 +33,6 @@ import i18n from 'i18n-js'; import * as Animatable from 'react-native-animatable'; import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons'; import LinearGradient from 'react-native-linear-gradient'; -import type {CustomThemeType} from '../../../managers/ThemeManager'; import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot'; import MascotPopup from '../../../components/Mascot/MascotPopup'; import AsyncStorageManager from '../../../managers/AsyncStorageManager'; @@ -47,17 +44,17 @@ import SpeechArrow from '../../../components/Mascot/SpeechArrow'; import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView'; type GameStatsType = { - score: number, - level: number, - time: number, + score: number; + level: number; + time: number; }; type PropsType = { - navigation: StackNavigationProp, + navigation: StackNavigationProp; route: { - params: GameStatsType, - }, - theme: CustomThemeType, + params: GameStatsType; + }; + theme: ReactNativePaper.Theme; }; class GameStartScreen extends React.Component { @@ -65,21 +62,24 @@ class GameStartScreen extends React.Component { scores: Array; - gameStats: GameStatsType | null; + gameStats?: GameStatsType; isHighScore: boolean; constructor(props: PropsType) { super(props); + this.isHighScore = false; this.gridManager = new GridManager(4, 4, props.theme); this.scores = AsyncStorageManager.getObject( AsyncStorageManager.PREFERENCES.gameScores.key, ); this.scores.sort((a: number, b: number): number => b - a); - if (props.route.params != null) this.recoverGameScore(); + if (props.route.params != null) { + this.recoverGameScore(); + } } - getPiecesBackground(): React.Node { + getPiecesBackground() { const {theme} = this.props; const gridList = []; for (let i = 0; i < 18; i += 1) { @@ -94,7 +94,7 @@ class GameStartScreen extends React.Component { width: '100%', height: '100%', }}> - {gridList.map((item: GridType, index: number): React.Node => { + {gridList.map((item: GridType, index: number) => { const size = 10 + Math.floor(Math.random() * 30); const top = Math.floor(Math.random() * 100); const rot = Math.floor(Math.random() * 360); @@ -129,7 +129,7 @@ class GameStartScreen extends React.Component { ); } - getPostGameContent(stats: GameStatsType): React.Node { + getPostGameContent(stats: GameStatsType) { const {props} = this; return ( { animated={this.isHighScore} style={{ width: this.isHighScore ? '50%' : '30%', - marginLeft: this.isHighScore ? 'auto' : null, - marginRight: this.isHighScore ? 'auto' : null, + marginLeft: this.isHighScore ? 'auto' : undefined, + marginRight: this.isHighScore ? 'auto' : undefined, }} /> { ); } - getWelcomeText(): React.Node { + getWelcomeText() { const {props} = this; return ( @@ -281,7 +281,7 @@ class GameStartScreen extends React.Component { ); } - getPodiumRender(place: 1 | 2 | 3, score: string): React.Node { + getPodiumRender(place: 1 | 2 | 3, score: string) { const {props} = this; let icon = 'podium-gold'; let color = props.theme.colors.gameGold; @@ -338,7 +338,7 @@ class GameStartScreen extends React.Component { {score} @@ -347,7 +347,7 @@ class GameStartScreen extends React.Component { ); } - getTopScoresRender(): React.Node { + getTopScoresRender() { const gold = this.scores.length > 0 ? this.scores[0] : '-'; const silver = this.scores.length > 1 ? this.scores[1] : '-'; const bronze = this.scores.length > 2 ? this.scores[2] : '-'; @@ -371,7 +371,7 @@ class GameStartScreen extends React.Component { ); } - getMainContent(): React.Node { + getMainContent() { const {props} = this; return ( @@ -415,7 +415,9 @@ class GameStartScreen extends React.Component { break; } } - if (this.scores.length > 3) this.scores.splice(3, 1); + if (this.scores.length > 3) { + this.scores.splice(3, 1); + } AsyncStorageManager.set( AsyncStorageManager.PREFERENCES.gameScores.key, this.scores, @@ -423,7 +425,7 @@ class GameStartScreen extends React.Component { } } - render(): React.Node { + render() { const {props} = this; return ( @@ -444,7 +446,6 @@ class GameStartScreen extends React.Component { message={i18n.t('screens.game.mascotDialog.message')} icon="gamepad-variant" buttons={{ - action: null, cancel: { message: i18n.t('screens.game.mascotDialog.button'), icon: 'check',