Improved game flow typing

This commit is contained in:
Arnaud Vergnet 2020-07-18 19:25:51 +02:00
parent 494b319f19
commit fe26ec0cc4
14 changed files with 164 additions and 197 deletions

View file

@ -5,17 +5,21 @@ import {Alert, View} from 'react-native';
import {IconButton, Text, withTheme} from 'react-native-paper'; import {IconButton, Text, withTheme} from 'react-native-paper';
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons"; import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import GameLogic from "./GameLogic"; import GameLogic from "./GameLogic";
import Grid from "./components/Grid"; import type {Grid} from "./components/GridComponent";
import GridComponent from "./components/GridComponent";
import Preview from "./components/Preview"; import Preview from "./components/Preview";
import i18n from "i18n-js"; import i18n from "i18n-js";
import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton"; import MaterialHeaderButtons, {Item} from "../../components/Overrides/CustomHeaderButton";
import {StackNavigationProp} from "@react-navigation/stack";
import type {CustomTheme} from "../../managers/ThemeManager";
type Props = { type Props = {
navigation: Object, navigation: StackNavigationProp,
theme: CustomTheme,
} }
type State = { type State = {
grid: Array<Array<Object>>, grid: Grid,
gameRunning: boolean, gameRunning: boolean,
gameTime: number, gameTime: number,
gameScore: number, gameScore: number,
@ -24,19 +28,11 @@ type State = {
class GameScreen extends React.Component<Props, State> { class GameScreen extends React.Component<Props, State> {
colors: Object;
logic: GameLogic; logic: GameLogic;
onTick: Function;
onClock: Function;
onGameEnd: Function;
updateGrid: Function;
updateGridScore: Function;
constructor(props) { constructor(props) {
super(props); super(props);
this.colors = props.theme.colors; this.logic = new GameLogic(20, 10, this.props.theme.colors);
this.logic = new GameLogic(20, 10, this.colors);
this.state = { this.state = {
grid: this.logic.getCurrentGrid(), grid: this.logic.getCurrentGrid(),
gameRunning: false, gameRunning: false,
@ -44,38 +40,32 @@ class GameScreen extends React.Component<Props, State> {
gameScore: 0, gameScore: 0,
gameLevel: 0, gameLevel: 0,
}; };
this.onTick = this.onTick.bind(this); this.props.navigation.addListener('blur', this.onScreenBlur);
this.onClock = this.onClock.bind(this); this.props.navigation.addListener('focus', this.onScreenFocus);
this.onGameEnd = this.onGameEnd.bind(this);
this.updateGrid = this.updateGrid.bind(this);
this.updateGridScore = this.updateGridScore.bind(this);
this.props.navigation.addListener('blur', this.onScreenBlur.bind(this));
this.props.navigation.addListener('focus', this.onScreenFocus.bind(this));
} }
componentDidMount() { componentDidMount() {
const rightButton = this.getRightButton.bind(this);
this.props.navigation.setOptions({ this.props.navigation.setOptions({
headerRight: rightButton, headerRight: this.getRightButton,
}); });
this.startGame(); this.startGame();
} }
getRightButton() { getRightButton = () => {
return <MaterialHeaderButtons> return <MaterialHeaderButtons>
<Item title="pause" iconName="pause" onPress={() => this.togglePause()}/> <Item title="pause" iconName="pause" onPress={this.togglePause}/>
</MaterialHeaderButtons>; </MaterialHeaderButtons>;
} }
/** /**
* Remove any interval on un-focus * Remove any interval on un-focus
*/ */
onScreenBlur() { onScreenBlur = () => {
if (!this.logic.isGamePaused()) if (!this.logic.isGamePaused())
this.logic.togglePause(); this.logic.togglePause();
} }
onScreenFocus() { onScreenFocus = () => {
if (!this.logic.isGameRunning()) if (!this.logic.isGameRunning())
this.startGame(); this.startGame();
else if (this.logic.isGamePaused()) else if (this.logic.isGamePaused())
@ -97,7 +87,7 @@ class GameScreen extends React.Component<Props, State> {
return format; return format;
} }
onTick(score: number, level: number, newGrid: Array<Array<Object>>) { onTick = (score: number, level: number, newGrid: Grid) => {
this.setState({ this.setState({
gameScore: score, gameScore: score,
gameLevel: level, gameLevel: level,
@ -105,50 +95,50 @@ class GameScreen extends React.Component<Props, State> {
}); });
} }
onClock(time: number) { onClock = (time: number) => {
this.setState({ this.setState({
gameTime: time, gameTime: time,
}); });
} }
updateGrid(newGrid: Array<Array<Object>>) { updateGrid = (newGrid: Grid) => {
this.setState({ this.setState({
grid: newGrid, grid: newGrid,
}); });
} }
updateGridScore(newGrid: Array<Array<Object>>, score: number) { updateGridScore = (newGrid: Grid, score: number) => {
this.setState({ this.setState({
grid: newGrid, grid: newGrid,
gameScore: score, gameScore: score,
}); });
} }
togglePause() { togglePause = () => {
this.logic.togglePause(); this.logic.togglePause();
if (this.logic.isGamePaused()) if (this.logic.isGamePaused())
this.showPausePopup(); this.showPausePopup();
} }
showPausePopup() { showPausePopup = () => {
Alert.alert( Alert.alert(
i18n.t("screens.game.pause"), i18n.t("screens.game.pause"),
i18n.t("screens.game.pauseMessage"), i18n.t("screens.game.pauseMessage"),
[ [
{text: i18n.t("screens.game.restart.text"), onPress: () => this.showRestartConfirm()}, {text: i18n.t("screens.game.restart.text"), onPress: this.showRestartConfirm},
{text: i18n.t("screens.game.resume"), onPress: () => this.togglePause()}, {text: i18n.t("screens.game.resume"), onPress: this.togglePause},
], ],
{cancelable: false}, {cancelable: false},
); );
} }
showRestartConfirm() { showRestartConfirm = () => {
Alert.alert( Alert.alert(
i18n.t("screens.game.restart.confirm"), i18n.t("screens.game.restart.confirm"),
i18n.t("screens.game.restart.confirmMessage"), i18n.t("screens.game.restart.confirmMessage"),
[ [
{text: i18n.t("screens.game.restart.confirmNo"), onPress: () => this.showPausePopup()}, {text: i18n.t("screens.game.restart.confirmNo"), onPress: this.showPausePopup},
{text: i18n.t("screens.game.restart.confirmYes"), onPress: () => this.startGame()}, {text: i18n.t("screens.game.restart.confirmYes"), onPress: this.startGame},
], ],
{cancelable: false}, {cancelable: false},
); );
@ -163,20 +153,20 @@ class GameScreen extends React.Component<Props, State> {
message, message,
[ [
{text: i18n.t("screens.game.gameOver.exit"), onPress: () => this.props.navigation.goBack()}, {text: i18n.t("screens.game.gameOver.exit"), onPress: () => this.props.navigation.goBack()},
{text: i18n.t("screens.game.restart.text"), onPress: () => this.startGame()}, {text: i18n.t("screens.game.restart.text"), onPress: this.startGame},
], ],
{cancelable: false}, {cancelable: false},
); );
} }
startGame() { startGame = () => {
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd); this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
this.setState({ this.setState({
gameRunning: true, gameRunning: true,
}); });
} }
onGameEnd(time: number, score: number, isRestart: boolean) { onGameEnd = (time: number, score: number, isRestart: boolean) => {
this.setState({ this.setState({
gameTime: time, gameTime: time,
gameScore: score, gameScore: score,
@ -187,6 +177,7 @@ class GameScreen extends React.Component<Props, State> {
} }
render() { render() {
const colors = this.props.theme.colors;
return ( return (
<View style={{ <View style={{
width: '100%', width: '100%',
@ -200,11 +191,11 @@ class GameScreen extends React.Component<Props, State> {
}}> }}>
<MaterialCommunityIcons <MaterialCommunityIcons
name={'timer'} name={'timer'}
color={this.colors.subtitle} color={colors.subtitle}
size={20}/> size={20}/>
<Text style={{ <Text style={{
marginLeft: 5, marginLeft: 5,
color: this.colors.subtitle color: colors.subtitle
}}>{this.getFormattedTime(this.state.gameTime)}</Text> }}>{this.getFormattedTime(this.state.gameTime)}</Text>
</View> </View>
<View style={{ <View style={{
@ -215,7 +206,7 @@ class GameScreen extends React.Component<Props, State> {
}}> }}>
<MaterialCommunityIcons <MaterialCommunityIcons
name={'gamepad'} name={'gamepad'}
color={this.colors.text} color={colors.text}
size={20}/> size={20}/>
<Text style={{ <Text style={{
marginLeft: 5 marginLeft: 5
@ -228,20 +219,20 @@ class GameScreen extends React.Component<Props, State> {
}}> }}>
<MaterialCommunityIcons <MaterialCommunityIcons
name={'star'} name={'star'}
color={this.colors.tetrisScore} color={colors.tetrisScore}
size={30}/> size={30}/>
<Text style={{ <Text style={{
marginLeft: 5, marginLeft: 5,
fontSize: 22, fontSize: 22,
}}>{this.state.gameScore}</Text> }}>{this.state.gameScore}</Text>
</View> </View>
<Grid <GridComponent
width={this.logic.getWidth()} width={this.logic.getWidth()}
height={this.logic.getHeight()} height={this.logic.getHeight()}
containerMaxHeight={'80%'} containerMaxHeight={'80%'}
containerMaxWidth={'60%'} containerMaxWidth={'60%'}
grid={this.state.grid} grid={this.state.grid}
backgroundColor={this.colors.tetrisBackground} backgroundColor={colors.tetrisBackground}
/> />
<View style={{ <View style={{
position: 'absolute', position: 'absolute',
@ -249,7 +240,7 @@ class GameScreen extends React.Component<Props, State> {
right: 5, right: 5,
}}> }}>
<Preview <Preview
next={this.logic.getNextPiecesPreviews()} items={this.logic.getNextPiecesPreviews()}
/> />
</View> </View>
<View style={{ <View style={{
@ -287,7 +278,7 @@ class GameScreen extends React.Component<Props, State> {
onPressIn={() => this.logic.downPressedIn(this.updateGridScore)} onPressIn={() => this.logic.downPressedIn(this.updateGridScore)}
onPress={() => this.logic.pressedOut()} onPress={() => this.logic.pressedOut()}
style={{marginLeft: 'auto'}} style={{marginLeft: 'auto'}}
color={this.colors.tetrisScore} color={colors.tetrisScore}
/> />
</View> </View>
</View> </View>

View file

@ -2,39 +2,37 @@
import Piece from "./Piece"; import Piece from "./Piece";
import ScoreManager from "./ScoreManager"; import ScoreManager from "./ScoreManager";
import type {coordinates} from './Shapes/BaseShape'; import type {Coordinates} from './Shapes/BaseShape';
import type {Grid} from "./components/GridComponent";
import type {Cell} from "./components/CellComponent";
export type cell = {color: string, isEmpty: boolean, key: string}; import type {CustomTheme} from "../../managers/ThemeManager";
export type grid = Array<Array<cell>>;
/** /**
* Class used to manage the game grid * Class used to manage the game grid
*
*/ */
export default class GridManager { export default class GridManager {
#currentGrid: grid; #currentGrid: Grid;
#colors: Object; #theme: CustomTheme;
/** /**
* Initializes a grid of the given size * Initializes a grid of the given size
* *
* @param width The grid width * @param width The grid width
* @param height The grid height * @param height The grid height
* @param colors Object containing current theme colors * @param theme Object containing current theme
*/ */
constructor(width: number, height: number, colors: Object) { constructor(width: number, height: number, theme: CustomTheme) {
this.#colors = colors; this.#theme = theme;
this.#currentGrid = this.getEmptyGrid(height, width); this.#currentGrid = this.getEmptyGrid(height, width);
} }
/** /**
* Get the current grid * Get the current grid
* *
* @return {grid} The current grid * @return {Grid} The current grid
*/ */
getCurrentGrid(): grid { getCurrentGrid(): Grid {
return this.#currentGrid; return this.#currentGrid;
} }
@ -42,13 +40,13 @@ export default class GridManager {
* Get a new empty grid line of the given size * Get a new empty grid line of the given size
* *
* @param width The line size * @param width The line size
* @return {Array<cell>} * @return {Array<Cell>}
*/ */
getEmptyLine(width: number): Array<cell> { getEmptyLine(width: number): Array<Cell> {
let line = []; let line = [];
for (let col = 0; col < width; col++) { for (let col = 0; col < width; col++) {
line.push({ line.push({
color: this.#colors.tetrisBackground, color: this.#theme.colors.tetrisBackground,
isEmpty: true, isEmpty: true,
key: col.toString(), key: col.toString(),
}); });
@ -61,9 +59,9 @@ export default class GridManager {
* *
* @param width The grid width * @param width The grid width
* @param height The grid height * @param height The grid height
* @return {grid} A new empty grid * @return {Grid} A new empty grid
*/ */
getEmptyGrid(height: number, width: number): grid { getEmptyGrid(height: number, width: number): Grid {
let grid = []; let grid = [];
for (let row = 0; row < height; row++) { for (let row = 0; row < height; row++) {
grid.push(this.getEmptyLine(width)); grid.push(this.getEmptyLine(width));
@ -91,21 +89,21 @@ export default class GridManager {
* Gets the lines to clear around the given piece's coordinates. * Gets the lines to clear around the given piece's coordinates.
* The piece's coordinates are used for optimization and to prevent checking the whole grid. * The piece's coordinates are used for optimization and to prevent checking the whole grid.
* *
* @param coord The piece's coordinates to check lines at * @param pos The piece's coordinates to check lines at
* @return {Array<number>} An array containing the line numbers to clear * @return {Array<number>} An array containing the line numbers to clear
*/ */
getLinesToClear(coord: Array<coordinates>): Array<number> { getLinesToClear(pos: Array<Coordinates>): Array<number> {
let rows = []; let rows = [];
for (let i = 0; i < coord.length; i++) { for (let i = 0; i < pos.length; i++) {
let isLineFull = true; let isLineFull = true;
for (let col = 0; col < this.#currentGrid[coord[i].y].length; col++) { for (let col = 0; col < this.#currentGrid[pos[i].y].length; col++) {
if (this.#currentGrid[coord[i].y][col].isEmpty) { if (this.#currentGrid[pos[i].y][col].isEmpty) {
isLineFull = false; isLineFull = false;
break; break;
} }
} }
if (isLineFull && rows.indexOf(coord[i].y) === -1) if (isLineFull && rows.indexOf(pos[i].y) === -1)
rows.push(coord[i].y); rows.push(pos[i].y);
} }
return rows; return rows;
} }

View file

@ -5,8 +5,10 @@ import ShapeO from "./Shapes/ShapeO";
import ShapeS from "./Shapes/ShapeS"; import ShapeS from "./Shapes/ShapeS";
import ShapeT from "./Shapes/ShapeT"; import ShapeT from "./Shapes/ShapeT";
import ShapeZ from "./Shapes/ShapeZ"; import ShapeZ from "./Shapes/ShapeZ";
import type {coordinates} from './Shapes/BaseShape'; import type {Coordinates} from './Shapes/BaseShape';
import type {grid} from './GridManager'; import BaseShape from "./Shapes/BaseShape";
import type {Grid} from "./components/GridComponent";
import type {CustomTheme} from "../../managers/ThemeManager";
/** /**
* Class used as an abstraction layer for shapes. * Class used as an abstraction layer for shapes.
@ -24,26 +26,26 @@ export default class Piece {
ShapeT, ShapeT,
ShapeZ, ShapeZ,
]; ];
#currentShape: Object; #currentShape: BaseShape;
#colors: Object; #theme: CustomTheme;
/** /**
* Initializes this piece's color and shape * Initializes this piece's color and shape
* *
* @param colors Object containing current theme colors * @param theme Object containing current theme
*/ */
constructor(colors: Object) { constructor(theme: CustomTheme) {
this.#currentShape = this.getRandomShape(colors); this.#currentShape = this.getRandomShape(theme);
this.#colors = colors; this.#theme = theme;
} }
/** /**
* Gets a random shape object * Gets a random shape object
* *
* @param colors Object containing current theme colors * @param theme Object containing current theme
*/ */
getRandomShape(colors: Object) { getRandomShape(theme: CustomTheme) {
return new this.#shapes[Math.floor(Math.random() * 7)](colors); return new this.#shapes[Math.floor(Math.random() * 7)](theme);
} }
/** /**
@ -51,13 +53,13 @@ export default class Piece {
* *
* @param grid The grid to remove the piece from * @param grid The grid to remove the piece from
*/ */
removeFromGrid(grid: grid) { removeFromGrid(grid: Grid) {
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true); const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(true);
for (let i = 0; i < coord.length; i++) { for (let i = 0; i < pos.length; i++) {
grid[coord[i].y][coord[i].x] = { grid[pos[i].y][pos[i].x] = {
color: this.#colors.tetrisBackground, color: this.#theme.colors.tetrisBackground,
isEmpty: true, isEmpty: true,
key: grid[coord[i].y][coord[i].x].key key: grid[pos[i].y][pos[i].x].key
}; };
} }
} }
@ -68,13 +70,13 @@ export default class Piece {
* @param grid The grid to add the piece to * @param grid The grid to add the piece to
* @param isPreview Should we use this piece's current position to determine the cells? * @param isPreview Should we use this piece's current position to determine the cells?
*/ */
toGrid(grid: grid, isPreview: boolean) { toGrid(grid: Grid, isPreview: boolean) {
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(!isPreview); const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(!isPreview);
for (let i = 0; i < coord.length; i++) { for (let i = 0; i < pos.length; i++) {
grid[coord[i].y][coord[i].x] = { grid[pos[i].y][pos[i].x] = {
color: this.#currentShape.getColor(), color: this.#currentShape.getColor(),
isEmpty: false, isEmpty: false,
key: grid[coord[i].y][coord[i].x].key key: grid[pos[i].y][pos[i].x].key
}; };
} }
} }
@ -87,15 +89,15 @@ export default class Piece {
* @param height The grid's height * @param height The grid's height
* @return {boolean} If the position is valid * @return {boolean} If the position is valid
*/ */
isPositionValid(grid: grid, width: number, height: number) { isPositionValid(grid: Grid, width: number, height: number) {
let isValid = true; let isValid = true;
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true); const pos: Array<Coordinates> = this.#currentShape.getCellsCoordinates(true);
for (let i = 0; i < coord.length; i++) { for (let i = 0; i < pos.length; i++) {
if (coord[i].x >= width if (pos[i].x >= width
|| coord[i].x < 0 || pos[i].x < 0
|| coord[i].y >= height || pos[i].y >= height
|| coord[i].y < 0 || pos[i].y < 0
|| !grid[coord[i].y][coord[i].x].isEmpty) { || !grid[pos[i].y][pos[i].x].isEmpty) {
isValid = false; isValid = false;
break; break;
} }
@ -114,7 +116,7 @@ export default class Piece {
* @param freezeCallback Callback to use if the piece should freeze itself * @param freezeCallback Callback to use if the piece should freeze itself
* @return {boolean} True if the move was valid, false otherwise * @return {boolean} True if the move was valid, false otherwise
*/ */
tryMove(x: number, y: number, grid: grid, width: number, height: number, freezeCallback: Function) { tryMove(x: number, y: number, grid: Grid, width: number, height: number, freezeCallback: () => void) {
if (x > 1) x = 1; // Prevent moving from more than one tile if (x > 1) x = 1; // Prevent moving from more than one tile
if (x < -1) x = -1; if (x < -1) x = -1;
if (y > 1) y = 1; if (y > 1) y = 1;
@ -143,7 +145,7 @@ export default class Piece {
* @param height The grid's height * @param height The grid's height
* @return {boolean} True if the rotation was valid, false otherwise * @return {boolean} True if the rotation was valid, false otherwise
*/ */
tryRotate(grid: grid, width: number, height: number) { tryRotate(grid: Grid, width: number, height: number) {
this.removeFromGrid(grid); this.removeFromGrid(grid);
this.#currentShape.rotate(true); this.#currentShape.rotate(true);
if (!this.isPositionValid(grid, width, height)) { if (!this.isPositionValid(grid, width, height)) {
@ -158,9 +160,9 @@ export default class Piece {
/** /**
* Gets this piece used cells coordinates * Gets this piece used cells coordinates
* *
* @return {Array<coordinates>} An array of coordinates * @return {Array<Coordinates>} An array of coordinates
*/ */
getCoordinates(): Array<coordinates> { getCoordinates(): Array<Coordinates> {
return this.#currentShape.getCellsCoordinates(true); return this.#currentShape.getCellsCoordinates(true);
} }
} }

View file

@ -1,10 +1,14 @@
// @flow // @flow
export type coordinates = { import type {CustomTheme} from "../../../managers/ThemeManager";
export type Coordinates = {
x: number, x: number,
y: number, y: number,
} }
type Shape = Array<Array<number>>;
/** /**
* Abstract class used to represent a BaseShape. * Abstract class used to represent a BaseShape.
* Abstract classes do not exist by default in Javascript: we force it by throwing errors in the constructor * Abstract classes do not exist by default in Javascript: we force it by throwing errors in the constructor
@ -12,16 +16,18 @@ export type coordinates = {
*/ */
export default class BaseShape { export default class BaseShape {
#currentShape: Array<Array<number>>; #currentShape: Shape;
#rotation: number; #rotation: number;
position: coordinates; position: Coordinates;
theme: CustomTheme;
/** /**
* Prevent instantiation if classname is BaseShape to force class to be abstract * Prevent instantiation if classname is BaseShape to force class to be abstract
*/ */
constructor() { constructor(theme: CustomTheme) {
if (this.constructor === BaseShape) if (this.constructor === BaseShape)
throw new Error("Abstract class can't be instantiated"); throw new Error("Abstract class can't be instantiated");
this.theme = theme;
this.#rotation = 0; this.#rotation = 0;
this.position = {x: 0, y: 0}; this.position = {x: 0, y: 0};
this.#currentShape = this.getShapes()[this.#rotation]; this.#currentShape = this.getShapes()[this.#rotation];
@ -41,7 +47,7 @@ export default class BaseShape {
* *
* Used by tests to read private fields * Used by tests to read private fields
*/ */
getShapes(): Array<Array<Array<number>>> { getShapes(): Array<Shape> {
throw new Error("Method 'getShapes()' must be implemented"); throw new Error("Method 'getShapes()' must be implemented");
} }
@ -50,7 +56,7 @@ export default class BaseShape {
* *
* Used by tests to read private fields * Used by tests to read private fields
*/ */
getCurrentShape(): Array<Array<number>> { getCurrentShape(): Shape {
return this.#currentShape; return this.#currentShape;
} }
@ -59,9 +65,9 @@ export default class BaseShape {
* This will return an array of coordinates representing the positions of the cells used by this object. * This will return an array of coordinates representing the positions of the cells used by this object.
* *
* @param isAbsolute Should we take into account the current position of the object? * @param isAbsolute Should we take into account the current position of the object?
* @return {Array<coordinates>} This object cells coordinates * @return {Array<Coordinates>} This object cells coordinates
*/ */
getCellsCoordinates(isAbsolute: boolean): Array<coordinates> { getCellsCoordinates(isAbsolute: boolean): Array<Coordinates> {
let coordinates = []; let coordinates = [];
for (let row = 0; row < this.#currentShape.length; row++) { for (let row = 0; row < this.#currentShape.length; row++) {
for (let col = 0; col < this.#currentShape[row].length; col++) { for (let col = 0; col < this.#currentShape[row].length; col++) {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeI extends BaseShape { export default class ShapeI extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisI; return this.theme.colors.tetrisI;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeJ extends BaseShape { export default class ShapeJ extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisJ; return this.theme.colors.tetrisJ;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeL extends BaseShape { export default class ShapeL extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisL; return this.theme.colors.tetrisL;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeO extends BaseShape { export default class ShapeO extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 4; this.position.x = 4;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisO; return this.theme.colors.tetrisO;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeS extends BaseShape { export default class ShapeS extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisS; return this.theme.colors.tetrisS;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeT extends BaseShape { export default class ShapeT extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisT; return this.theme.colors.tetrisT;
} }
getShapes() { getShapes() {

View file

@ -1,19 +1,17 @@
// @flow // @flow
import BaseShape from "./BaseShape"; import BaseShape from "./BaseShape";
import type {CustomTheme} from "../../../managers/ThemeManager";
export default class ShapeZ extends BaseShape { export default class ShapeZ extends BaseShape {
#colors: Object; constructor(theme: CustomTheme) {
super(theme);
constructor(colors: Object) {
super();
this.position.x = 3; this.position.x = 3;
this.#colors = colors;
} }
getColor(): string { getColor(): string {
return this.#colors.tetrisZ; return this.theme.colors.tetrisZ;
} }
getShapes() { getShapes() {

View file

@ -3,28 +3,25 @@
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {withTheme} from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import type {CustomTheme} from "../../../managers/ThemeManager";
export type Cell = {color: string, isEmpty: boolean, key: string};
type Props = { type Props = {
item: Object cell: Cell,
theme: CustomTheme,
} }
class Cell extends React.PureComponent<Props> { class CellComponent extends React.PureComponent<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
render() { render() {
const item = this.props.item; const item = this.props.cell;
return ( return (
<View <View
style={{ style={{
flex: 1, flex: 1,
backgroundColor: item.isEmpty ? 'transparent' : item.color, backgroundColor: item.isEmpty ? 'transparent' : item.color,
borderColor: item.isEmpty ? 'transparent' : this.colors.tetrisBorder, borderColor: item.isEmpty ? 'transparent' : this.props.theme.colors.tetrisBorder,
borderStyle: 'solid', borderStyle: 'solid',
borderRadius: 2, borderRadius: 2,
borderWidth: 1, borderWidth: 1,
@ -38,4 +35,4 @@ class Cell extends React.PureComponent<Props> {
} }
export default withTheme(Cell); export default withTheme(CellComponent);

View file

@ -3,10 +3,12 @@
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {withTheme} from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import Cell from "./Cell"; import type {Cell} from "./CellComponent";
import CellComponent from "./CellComponent";
export type Grid = Array<Array<CellComponent>>;
type Props = { type Props = {
navigation: Object,
grid: Array<Array<Object>>, grid: Array<Array<Object>>,
backgroundColor: string, backgroundColor: string,
height: number, height: number,
@ -15,14 +17,7 @@ type Props = {
containerMaxWidth: number | string, containerMaxWidth: number | string,
} }
class Grid extends React.Component<Props> { class GridComponent extends React.Component<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getRow(rowNumber: number) { getRow(rowNumber: number) {
let cells = this.props.grid[rowNumber].map(this.getCellRender); let cells = this.props.grid[rowNumber].map(this.getCellRender);
@ -39,8 +34,8 @@ class Grid extends React.Component<Props> {
); );
} }
getCellRender = (item: Object) => { getCellRender = (item: Cell) => {
return <Cell item={item} key={item.key}/>; return <CellComponent cell={item}/>;
}; };
getGrid() { getGrid() {
@ -67,4 +62,4 @@ class Grid extends React.Component<Props> {
} }
} }
export default withTheme(Grid); export default withTheme(GridComponent);

View file

@ -3,33 +3,25 @@
import * as React from 'react'; import * as React from 'react';
import {View} from 'react-native'; import {View} from 'react-native';
import {withTheme} from 'react-native-paper'; import {withTheme} from 'react-native-paper';
import Grid from "./Grid"; import type {Grid} from "./GridComponent";
import GridComponent from "./GridComponent";
type Props = { type Props = {
next: Object, items: Array<Grid>,
} }
class Preview extends React.PureComponent<Props> { class Preview extends React.PureComponent<Props> {
colors: Object;
constructor(props) {
super(props);
this.colors = props.theme.colors;
}
getGrids() { getGrids() {
let grids = []; let grids = [];
for (let i = 0; i < this.props.next.length; i++) { for (let i = 0; i < this.props.items.length; i++) {
grids.push( grids.push(this.getGridRender(this.props.items[i], i));
this.getGridRender(this.props.next[i], i)
);
} }
return grids; return grids;
} }
getGridRender(item: Object, index: number) { getGridRender(item: Grid, index: number) {
return <Grid return <GridComponent
width={item[0].length} width={item[0].length}
height={item.length} height={item.length}
grid={item} grid={item}
@ -41,7 +33,7 @@ class Preview extends React.PureComponent<Props> {
}; };
render() { render() {
if (this.props.next.length > 0) { if (this.props.items.length > 0) {
return ( return (
<View> <View>
{this.getGrids()} {this.getGrids()}