forked from vergnet/application-amicale
Improved game flow typing
This commit is contained in:
parent
494b319f19
commit
fe26ec0cc4
14 changed files with 164 additions and 197 deletions
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
|
@ -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);
|
|
@ -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()}
|
||||||
|
|
Loading…
Reference in a new issue