Update game to use TypeScript

This commit is contained in:
Arnaud Vergnet 2020-09-22 23:13:09 +02:00
parent c198a40148
commit fde9a12ef9
18 changed files with 326 additions and 313 deletions

View file

@ -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<Array<number>>;
@ -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<ShapeType> {
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];
}

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<ShapeType> {
return [
[

View file

@ -17,35 +17,29 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<PropsType> {
render(): React.Node {
const {props} = this;
const item = props.cell;
return (
<View
style={{
flex: 1,
backgroundColor: item.isEmpty ? 'transparent' : item.color,
borderColor: 'transparent',
borderRadius: 4,
borderWidth: 1,
aspectRatio: 1,
}}
/>
);
}
function CellComponent(props: PropsType) {
const item = props.cell;
return (
<View
style={{
flex: 1,
backgroundColor: item.isEmpty ? 'transparent' : item.color,
borderColor: 'transparent',
borderRadius: 4,
borderWidth: 1,
aspectRatio: 1,
}}
/>
);
}
export default withTheme(CellComponent);
export default CellComponent;

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
// @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<Array<CellComponent>>;
type PropsType = {
grid: Array<Array<CellType>>,
height: number,
width: number,
style: ViewStyle,
};
class GridComponent extends React.Component<PropsType> {
getRow(rowNumber: number): React.Node {
const {grid} = this.props;
return (
<View style={{flexDirection: 'row'}} key={rowNumber.toString()}>
{grid[rowNumber].map(this.getCellRender)}
</View>
);
}
getCellRender = (item: CellType): React.Node => {
return <CellComponent cell={item} key={item.key} />;
};
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 (
<View
style={{
aspectRatio: width / height,
borderRadius: 4,
...style,
}}>
{this.getGrid()}
</View>
);
}
}
export default withTheme(GridComponent);

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
import * as React from 'react';
import {View, ViewStyle} from 'react-native';
import type {CellType} from './CellComponent';
import CellComponent from './CellComponent';
export type GridType = Array<Array<CellType>>;
type PropsType = {
grid: GridType;
height: number;
width: number;
style: ViewStyle;
};
const getCellRender = (item: CellType) => {
return <CellComponent cell={item} key={item.key} />;
};
function getRow(grid: GridType, rowNumber: number) {
return (
<View style={{flexDirection: 'row'}} key={rowNumber.toString()}>
{grid[rowNumber].map(getCellRender)}
</View>
);
}
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 (
<View
style={{
aspectRatio: width / height,
borderRadius: 4,
...style,
}}>
{getGrid(grid, height)}
</View>
);
}
export default GridComponent;

View file

@ -17,53 +17,48 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<GridType>,
style: ViewStyle,
items: Array<GridType>;
style: ViewStyle;
};
function getGridRender(item: GridType, index: number) {
return (
<GridComponent
width={item[0].length}
height={item.length}
grid={item}
style={{
marginRight: 5,
marginLeft: 5,
marginBottom: 5,
}}
key={index.toString()}
/>
);
}
function getGrids(items: Array<GridType>) {
const grids: Array<React.ReactNode> = [];
items.forEach((item: GridType, index: number) => {
grids.push(getGridRender(item, index));
});
return grids;
}
class Preview extends React.PureComponent<PropsType> {
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 (
<GridComponent
width={item[0].length}
height={item.length}
grid={item}
style={{
marginRight: 5,
marginLeft: 5,
marginBottom: 5,
}}
key={index.toString()}
/>
);
}
render(): React.Node {
render() {
const {style, items} = this.props;
if (items.length > 0) {
return <View style={style}>{this.getGrids()}</View>;
return <View style={style}>{getGrids(items)}</View>;
}
return null;
}
}
export default withTheme(Preview);
export default Preview;

View file

@ -17,12 +17,9 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<GridType> {
@ -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;

View file

@ -17,14 +17,11 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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;
}

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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;
}

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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) {

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<any>;
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<OptionsDialogButtonType>,
onDialogDismiss: () => void,
dialogVisible: boolean;
dialogTitle: string;
dialogMessage: string;
dialogButtons: Array<OptionsDialogButtonType>;
onDialogDismiss: () => void;
};
class GameMainScreen extends React.Component<PropsType, StateType> {
@ -62,11 +59,13 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
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<PropsType, StateType> {
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<PropsType, StateType> {
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<PropsType, StateType> {
this.logic.endGame(true);
}
getRightButton = (): React.Node => {
getRightButton = () => {
return (
<MaterialHeaderButtons>
<Item title="pause" iconName="pause" onPress={this.togglePause} />
@ -136,15 +137,16 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
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 (
<View
@ -219,7 +221,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
);
}
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<PropsType, StateType> {
);
}
getControlButtons(): React.Node {
getControlButtons() {
const {props} = this;
return (
<View
@ -353,8 +355,8 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
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<PropsType, StateType> {
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<PropsType, StateType> {
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
};
render(): React.Node {
render() {
const {props, state} = this;
return (
<View style={{flex: 1}}>

View file

@ -17,8 +17,6 @@
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
*/
// @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<any>;
route: {
params: GameStatsType,
},
theme: CustomThemeType,
params: GameStatsType;
};
theme: ReactNativePaper.Theme;
};
class GameStartScreen extends React.Component<PropsType> {
@ -65,21 +62,24 @@ class GameStartScreen extends React.Component<PropsType> {
scores: Array<number>;
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<PropsType> {
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<PropsType> {
);
}
getPostGameContent(stats: GameStatsType): React.Node {
getPostGameContent(stats: GameStatsType) {
const {props} = this;
return (
<View
@ -141,8 +141,8 @@ class GameStartScreen extends React.Component<PropsType> {
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,
}}
/>
<SpeechArrow
@ -235,7 +235,7 @@ class GameStartScreen extends React.Component<PropsType> {
);
}
getWelcomeText(): React.Node {
getWelcomeText() {
const {props} = this;
return (
<View>
@ -281,7 +281,7 @@ class GameStartScreen extends React.Component<PropsType> {
);
}
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<PropsType> {
<Text
style={{
textAlign: 'center',
fontWeight: place === 1 ? 'bold' : null,
fontWeight: place === 1 ? 'bold' : undefined,
fontSize,
}}>
{score}
@ -347,7 +347,7 @@ class GameStartScreen extends React.Component<PropsType> {
);
}
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<PropsType> {
);
}
getMainContent(): React.Node {
getMainContent() {
const {props} = this;
return (
<View style={{flex: 1}}>
@ -415,7 +415,9 @@ class GameStartScreen extends React.Component<PropsType> {
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<PropsType> {
}
}
render(): React.Node {
render() {
const {props} = this;
return (
<View style={{flex: 1}}>
@ -444,7 +446,6 @@ class GameStartScreen extends React.Component<PropsType> {
message={i18n.t('screens.game.mascotDialog.message')}
icon="gamepad-variant"
buttons={{
action: null,
cancel: {
message: i18n.t('screens.game.mascotDialog.button'),
icon: 'check',