forked from vergnet/application-amicale
Update game to use TypeScript
This commit is contained in:
parent
c198a40148
commit
fde9a12ef9
18 changed files with 326 additions and 313 deletions
|
@ -19,11 +19,9 @@
|
||||||
|
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
|
|
||||||
export type CoordinatesType = {
|
export type CoordinatesType = {
|
||||||
x: number,
|
x: number;
|
||||||
y: number,
|
y: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShapeType = Array<Array<number>>;
|
export type ShapeType = Array<Array<number>>;
|
||||||
|
@ -40,14 +38,15 @@ export default class BaseShape {
|
||||||
|
|
||||||
position: CoordinatesType;
|
position: CoordinatesType;
|
||||||
|
|
||||||
theme: CustomThemeType;
|
theme: ReactNativePaper.Theme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
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.theme = theme;
|
||||||
this.#rotation = 0;
|
this.#rotation = 0;
|
||||||
this.position = {x: 0, y: 0};
|
this.position = {x: 0, y: 0};
|
||||||
|
@ -58,7 +57,6 @@ export default class BaseShape {
|
||||||
* Gets this shape's color.
|
* Gets this shape's color.
|
||||||
* Must be implemented by child class
|
* Must be implemented by child class
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getColor(): string {
|
getColor(): string {
|
||||||
throw new Error("Method 'getColor()' must be implemented");
|
throw new Error("Method 'getColor()' must be implemented");
|
||||||
}
|
}
|
||||||
|
@ -69,7 +67,6 @@ export default class BaseShape {
|
||||||
*
|
*
|
||||||
* Used by tests to read private fields
|
* Used by tests to read private fields
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
throw new Error("Method 'getShapes()' must be implemented");
|
throw new Error("Method 'getShapes()' must be implemented");
|
||||||
}
|
}
|
||||||
|
@ -98,7 +95,9 @@ export default class BaseShape {
|
||||||
x: this.position.x + col,
|
x: this.position.x + col,
|
||||||
y: this.position.y + row,
|
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?
|
* @param isForward Should we rotate clockwise?
|
||||||
*/
|
*/
|
||||||
rotate(isForward: boolean) {
|
rotate(isForward: boolean) {
|
||||||
if (isForward) this.#rotation += 1;
|
if (isForward) {
|
||||||
else this.#rotation -= 1;
|
this.#rotation += 1;
|
||||||
if (this.#rotation > 3) this.#rotation = 0;
|
} else {
|
||||||
else if (this.#rotation < 0) this.#rotation = 3;
|
this.#rotation -= 1;
|
||||||
|
}
|
||||||
|
if (this.#rotation > 3) {
|
||||||
|
this.#rotation = 0;
|
||||||
|
} else if (this.#rotation < 0) {
|
||||||
|
this.#rotation = 3;
|
||||||
|
}
|
||||||
this.#currentShape = this.getShapes()[this.#rotation];
|
this.#currentShape = this.getShapes()[this.#rotation];
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeI extends BaseShape {
|
export default class ShapeI extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeI extends BaseShape {
|
||||||
return this.theme.colors.tetrisI;
|
return this.theme.colors.tetrisI;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeJ extends BaseShape {
|
export default class ShapeJ extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeJ extends BaseShape {
|
||||||
return this.theme.colors.tetrisJ;
|
return this.theme.colors.tetrisJ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeL extends BaseShape {
|
export default class ShapeL extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeL extends BaseShape {
|
||||||
return this.theme.colors.tetrisL;
|
return this.theme.colors.tetrisL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeO extends BaseShape {
|
export default class ShapeO extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 4;
|
this.position.x = 4;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeO extends BaseShape {
|
||||||
return this.theme.colors.tetrisO;
|
return this.theme.colors.tetrisO;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeS extends BaseShape {
|
export default class ShapeS extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeS extends BaseShape {
|
||||||
return this.theme.colors.tetrisS;
|
return this.theme.colors.tetrisS;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeT extends BaseShape {
|
export default class ShapeT extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeT extends BaseShape {
|
||||||
return this.theme.colors.tetrisT;
|
return this.theme.colors.tetrisT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import BaseShape from './BaseShape';
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {ShapeType} from './BaseShape';
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeZ extends BaseShape {
|
export default class ShapeZ extends BaseShape {
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,6 @@ export default class ShapeZ extends BaseShape {
|
||||||
return this.theme.colors.tetrisZ;
|
return this.theme.colors.tetrisZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
getShapes(): Array<ShapeType> {
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
|
@ -17,35 +17,29 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
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';
|
|
||||||
|
|
||||||
export type CellType = {color: string, isEmpty: boolean, key: string};
|
export type CellType = {color: string; isEmpty: boolean; key: string};
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
cell: CellType,
|
cell: CellType;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CellComponent extends React.PureComponent<PropsType> {
|
function CellComponent(props: PropsType) {
|
||||||
render(): React.Node {
|
const item = props.cell;
|
||||||
const {props} = this;
|
return (
|
||||||
const item = props.cell;
|
<View
|
||||||
return (
|
style={{
|
||||||
<View
|
flex: 1,
|
||||||
style={{
|
backgroundColor: item.isEmpty ? 'transparent' : item.color,
|
||||||
flex: 1,
|
borderColor: 'transparent',
|
||||||
backgroundColor: item.isEmpty ? 'transparent' : item.color,
|
borderRadius: 4,
|
||||||
borderColor: 'transparent',
|
borderWidth: 1,
|
||||||
borderRadius: 4,
|
aspectRatio: 1,
|
||||||
borderWidth: 1,
|
}}
|
||||||
aspectRatio: 1,
|
/>
|
||||||
}}
|
);
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(CellComponent);
|
export default CellComponent;
|
|
@ -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);
|
|
68
src/screens/Game/components/GridComponent.tsx
Normal file
68
src/screens/Game/components/GridComponent.tsx
Normal 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;
|
|
@ -17,53 +17,48 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from 'react-native';
|
import {View, ViewStyle} from 'react-native';
|
||||||
import {withTheme} from 'react-native-paper';
|
|
||||||
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
|
||||||
import type {GridType} from './GridComponent';
|
import type {GridType} from './GridComponent';
|
||||||
import GridComponent from './GridComponent';
|
import GridComponent from './GridComponent';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
items: Array<GridType>,
|
items: Array<GridType>;
|
||||||
style: ViewStyle,
|
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> {
|
class Preview extends React.PureComponent<PropsType> {
|
||||||
getGrids(): React.Node {
|
render() {
|
||||||
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 {
|
|
||||||
const {style, items} = this.props;
|
const {style, items} = this.props;
|
||||||
if (items.length > 0) {
|
if (items.length > 0) {
|
||||||
return <View style={style}>{this.getGrids()}</View>;
|
return <View style={style}>{getGrids(items)}</View>;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(Preview);
|
export default Preview;
|
|
@ -17,12 +17,9 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import Piece from './Piece';
|
import Piece from './Piece';
|
||||||
import ScoreManager from './ScoreManager';
|
import ScoreManager from './ScoreManager';
|
||||||
import GridManager from './GridManager';
|
import GridManager from './GridManager';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {GridType} from '../components/GridComponent';
|
import type {GridType} from '../components/GridComponent';
|
||||||
|
|
||||||
export type TickCallbackType = (
|
export type TickCallbackType = (
|
||||||
|
@ -58,15 +55,15 @@ export default class GameLogic {
|
||||||
|
|
||||||
gameTime: number;
|
gameTime: number;
|
||||||
|
|
||||||
currentObject: Piece;
|
currentObject?: Piece;
|
||||||
|
|
||||||
gameTick: number;
|
gameTick: number;
|
||||||
|
|
||||||
gameTickInterval: IntervalID;
|
gameTickInterval?: NodeJS.Timeout;
|
||||||
|
|
||||||
gameTimeInterval: IntervalID;
|
gameTimeInterval?: NodeJS.Timeout;
|
||||||
|
|
||||||
pressInInterval: TimeoutID;
|
pressInInterval?: NodeJS.Timeout;
|
||||||
|
|
||||||
isPressedIn: boolean;
|
isPressedIn: boolean;
|
||||||
|
|
||||||
|
@ -78,15 +75,19 @@ export default class GameLogic {
|
||||||
|
|
||||||
nextPiecesCount: number;
|
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.height = height;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.gameRunning = false;
|
this.gameRunning = false;
|
||||||
|
@ -121,12 +122,16 @@ export default class GameLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
onFreeze = () => {
|
onFreeze = () => {
|
||||||
this.gridManager.freezeTetromino(this.currentObject, this.scoreManager);
|
if (this.currentObject) {
|
||||||
|
this.gridManager.freezeTetromino(this.currentObject, this.scoreManager);
|
||||||
|
}
|
||||||
this.createTetromino();
|
this.createTetromino();
|
||||||
};
|
};
|
||||||
|
|
||||||
setNewGameTick(level: number) {
|
setNewGameTick(level: number) {
|
||||||
if (level >= GameLogic.levelTicks.length) return;
|
if (level >= GameLogic.levelTicks.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.gameTick = GameLogic.levelTicks[level];
|
this.gameTick = GameLogic.levelTicks[level];
|
||||||
this.stopTick();
|
this.stopTick();
|
||||||
this.startTick();
|
this.startTick();
|
||||||
|
@ -145,11 +150,15 @@ export default class GameLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
stopClock() {
|
stopClock() {
|
||||||
clearInterval(this.gameTimeInterval);
|
if (this.gameTimeInterval) {
|
||||||
|
clearInterval(this.gameTimeInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTick() {
|
stopTick() {
|
||||||
clearInterval(this.gameTickInterval);
|
if (this.gameTickInterval) {
|
||||||
|
clearInterval(this.gameTickInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopGameTime() {
|
stopGameTime() {
|
||||||
|
@ -162,27 +171,34 @@ export default class GameLogic {
|
||||||
this.startTick();
|
this.startTick();
|
||||||
}
|
}
|
||||||
|
|
||||||
onTick(callback: TickCallbackType) {
|
onTick(callback?: TickCallbackType) {
|
||||||
this.currentObject.tryMove(
|
if (this.currentObject) {
|
||||||
0,
|
this.currentObject.tryMove(
|
||||||
1,
|
0,
|
||||||
this.gridManager.getCurrentGrid(),
|
1,
|
||||||
this.getWidth(),
|
this.gridManager.getCurrentGrid(),
|
||||||
this.getHeight(),
|
this.getWidth(),
|
||||||
this.onFreeze,
|
this.getHeight(),
|
||||||
);
|
this.onFreeze,
|
||||||
callback(
|
);
|
||||||
this.scoreManager.getScore(),
|
}
|
||||||
this.scoreManager.getLevel(),
|
if (callback) {
|
||||||
this.gridManager.getCurrentGrid(),
|
callback(
|
||||||
);
|
this.scoreManager.getScore(),
|
||||||
if (this.scoreManager.canLevelUp())
|
this.scoreManager.getLevel(),
|
||||||
|
this.gridManager.getCurrentGrid(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (this.scoreManager.canLevelUp()) {
|
||||||
this.setNewGameTick(this.scoreManager.getLevel());
|
this.setNewGameTick(this.scoreManager.getLevel());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClock(callback: ClockCallbackType) {
|
onClock(callback?: ClockCallbackType) {
|
||||||
this.gameTime += 1;
|
this.gameTime += 1;
|
||||||
callback(this.gameTime);
|
if (callback) {
|
||||||
|
callback(this.gameTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canUseInput(): boolean {
|
canUseInput(): boolean {
|
||||||
|
@ -210,15 +226,19 @@ export default class GameLogic {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
) {
|
) {
|
||||||
if (!this.canUseInput() || !this.isPressedIn) return;
|
if (!this.canUseInput() || !this.isPressedIn) {
|
||||||
const moved = this.currentObject.tryMove(
|
return;
|
||||||
x,
|
}
|
||||||
y,
|
const moved =
|
||||||
this.gridManager.getCurrentGrid(),
|
this.currentObject &&
|
||||||
this.getWidth(),
|
this.currentObject.tryMove(
|
||||||
this.getHeight(),
|
x,
|
||||||
this.onFreeze,
|
y,
|
||||||
);
|
this.gridManager.getCurrentGrid(),
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
this.onFreeze,
|
||||||
|
);
|
||||||
if (moved) {
|
if (moved) {
|
||||||
if (y === 1) {
|
if (y === 1) {
|
||||||
this.scoreManager.incrementScore();
|
this.scoreManager.incrementScore();
|
||||||
|
@ -226,7 +246,9 @@ export default class GameLogic {
|
||||||
this.gridManager.getCurrentGrid(),
|
this.gridManager.getCurrentGrid(),
|
||||||
this.scoreManager.getScore(),
|
this.scoreManager.getScore(),
|
||||||
);
|
);
|
||||||
} else callback(this.gridManager.getCurrentGrid());
|
} else {
|
||||||
|
callback(this.gridManager.getCurrentGrid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.pressInInterval = setTimeout(
|
this.pressInInterval = setTimeout(
|
||||||
() => {
|
() => {
|
||||||
|
@ -238,20 +260,26 @@ export default class GameLogic {
|
||||||
|
|
||||||
pressedOut() {
|
pressedOut() {
|
||||||
this.isPressedIn = false;
|
this.isPressedIn = false;
|
||||||
clearTimeout(this.pressInInterval);
|
if (this.pressInInterval) {
|
||||||
|
clearTimeout(this.pressInInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rotatePressed(callback: MovementCallbackType) {
|
rotatePressed(callback: MovementCallbackType) {
|
||||||
if (!this.canUseInput()) return;
|
if (!this.canUseInput()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
this.currentObject &&
|
||||||
this.currentObject.tryRotate(
|
this.currentObject.tryRotate(
|
||||||
this.gridManager.getCurrentGrid(),
|
this.gridManager.getCurrentGrid(),
|
||||||
this.getWidth(),
|
this.getWidth(),
|
||||||
this.getHeight(),
|
this.getHeight(),
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
callback(this.gridManager.getCurrentGrid());
|
callback(this.gridManager.getCurrentGrid());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextPiecesPreviews(): Array<GridType> {
|
getNextPiecesPreviews(): Array<GridType> {
|
||||||
|
@ -266,7 +294,10 @@ export default class GameLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
recoverNextPiece() {
|
recoverNextPiece() {
|
||||||
this.currentObject = this.nextPieces.shift();
|
const next = this.nextPieces.shift();
|
||||||
|
if (next) {
|
||||||
|
this.currentObject = next;
|
||||||
|
}
|
||||||
this.generateNextPieces();
|
this.generateNextPieces();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,27 +311,36 @@ export default class GameLogic {
|
||||||
this.pressedOut();
|
this.pressedOut();
|
||||||
this.recoverNextPiece();
|
this.recoverNextPiece();
|
||||||
if (
|
if (
|
||||||
|
this.currentObject &&
|
||||||
!this.currentObject.isPositionValid(
|
!this.currentObject.isPositionValid(
|
||||||
this.gridManager.getCurrentGrid(),
|
this.gridManager.getCurrentGrid(),
|
||||||
this.getWidth(),
|
this.getWidth(),
|
||||||
this.getHeight(),
|
this.getHeight(),
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
this.endGame(false);
|
this.endGame(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePause() {
|
togglePause() {
|
||||||
if (!this.gameRunning) return;
|
if (!this.gameRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.gamePaused = !this.gamePaused;
|
this.gamePaused = !this.gamePaused;
|
||||||
if (this.gamePaused) this.stopGameTime();
|
if (this.gamePaused) {
|
||||||
else this.startGameTime();
|
this.stopGameTime();
|
||||||
|
} else {
|
||||||
|
this.startGameTime();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endGame(isRestart: boolean) {
|
endGame(isRestart: boolean) {
|
||||||
this.gameRunning = false;
|
this.gameRunning = false;
|
||||||
this.gamePaused = false;
|
this.gamePaused = false;
|
||||||
this.stopGameTime();
|
this.stopGameTime();
|
||||||
this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
|
if (this.endCallback) {
|
||||||
|
this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(
|
startGame(
|
||||||
|
@ -308,7 +348,9 @@ export default class GameLogic {
|
||||||
clockCallback: ClockCallbackType,
|
clockCallback: ClockCallbackType,
|
||||||
endCallback: EndCallbackType,
|
endCallback: EndCallbackType,
|
||||||
) {
|
) {
|
||||||
if (this.gameRunning) this.endGame(true);
|
if (this.gameRunning) {
|
||||||
|
this.endGame(true);
|
||||||
|
}
|
||||||
this.gameRunning = true;
|
this.gameRunning = true;
|
||||||
this.gamePaused = false;
|
this.gamePaused = false;
|
||||||
this.gameTime = 0;
|
this.gameTime = 0;
|
|
@ -17,14 +17,11 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import Piece from './Piece';
|
import Piece from './Piece';
|
||||||
import ScoreManager from './ScoreManager';
|
import ScoreManager from './ScoreManager';
|
||||||
import type {CoordinatesType} from '../Shapes/BaseShape';
|
import type {CoordinatesType} from '../Shapes/BaseShape';
|
||||||
import type {GridType} from '../components/GridComponent';
|
import type {GridType} from '../components/GridComponent';
|
||||||
import type {CellType} from '../components/CellComponent';
|
import type {CellType} from '../components/CellComponent';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to manage the game grid
|
* Class used to manage the game grid
|
||||||
|
@ -32,7 +29,7 @@ import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
export default class GridManager {
|
export default class GridManager {
|
||||||
#currentGrid: GridType;
|
#currentGrid: GridType;
|
||||||
|
|
||||||
#theme: CustomThemeType;
|
#theme: ReactNativePaper.Theme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a grid of the given size
|
* Initializes a grid of the given size
|
||||||
|
@ -41,7 +38,7 @@ export default class GridManager {
|
||||||
* @param height The grid height
|
* @param height The grid height
|
||||||
* @param theme Object containing current theme
|
* @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.#theme = theme;
|
||||||
this.#currentGrid = this.getEmptyGrid(height, width);
|
this.#currentGrid = this.getEmptyGrid(height, width);
|
||||||
}
|
}
|
||||||
|
@ -121,7 +118,9 @@ export default class GridManager {
|
||||||
break;
|
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;
|
return rows;
|
||||||
}
|
}
|
|
@ -17,8 +17,6 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import ShapeL from '../Shapes/ShapeL';
|
import ShapeL from '../Shapes/ShapeL';
|
||||||
import ShapeI from '../Shapes/ShapeI';
|
import ShapeI from '../Shapes/ShapeI';
|
||||||
import ShapeJ from '../Shapes/ShapeJ';
|
import ShapeJ from '../Shapes/ShapeJ';
|
||||||
|
@ -29,7 +27,6 @@ import ShapeZ from '../Shapes/ShapeZ';
|
||||||
import type {CoordinatesType} from '../Shapes/BaseShape';
|
import type {CoordinatesType} from '../Shapes/BaseShape';
|
||||||
import BaseShape from '../Shapes/BaseShape';
|
import BaseShape from '../Shapes/BaseShape';
|
||||||
import type {GridType} from '../components/GridComponent';
|
import type {GridType} from '../components/GridComponent';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used as an abstraction layer for shapes.
|
* Class used as an abstraction layer for shapes.
|
||||||
|
@ -41,14 +38,14 @@ export default class Piece {
|
||||||
|
|
||||||
currentShape: BaseShape;
|
currentShape: BaseShape;
|
||||||
|
|
||||||
theme: CustomThemeType;
|
theme: ReactNativePaper.Theme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes this piece's color and shape
|
* Initializes this piece's color and shape
|
||||||
*
|
*
|
||||||
* @param theme Object containing current theme
|
* @param theme Object containing current theme
|
||||||
*/
|
*/
|
||||||
constructor(theme: CustomThemeType) {
|
constructor(theme: ReactNativePaper.Theme) {
|
||||||
this.currentShape = this.getRandomShape(theme);
|
this.currentShape = this.getRandomShape(theme);
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +55,7 @@ export default class Piece {
|
||||||
*
|
*
|
||||||
* @param theme Object containing current theme
|
* @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);
|
return new this.shapes[Math.floor(Math.random() * 7)](theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +69,6 @@ export default class Piece {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
pos.forEach((coordinates: CoordinatesType) => {
|
pos.forEach((coordinates: CoordinatesType) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
grid[coordinates.y][coordinates.x] = {
|
grid[coordinates.y][coordinates.x] = {
|
||||||
color: this.theme.colors.tetrisBackground,
|
color: this.theme.colors.tetrisBackground,
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
|
@ -92,7 +88,6 @@ export default class Piece {
|
||||||
!isPreview,
|
!isPreview,
|
||||||
);
|
);
|
||||||
pos.forEach((coordinates: CoordinatesType) => {
|
pos.forEach((coordinates: CoordinatesType) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
|
||||||
grid[coordinates.y][coordinates.x] = {
|
grid[coordinates.y][coordinates.x] = {
|
||||||
color: this.currentShape.getColor(),
|
color: this.currentShape.getColor(),
|
||||||
isEmpty: false,
|
isEmpty: false,
|
||||||
|
@ -150,21 +145,35 @@ export default class Piece {
|
||||||
): boolean {
|
): boolean {
|
||||||
let newX = x;
|
let newX = x;
|
||||||
let newY = y;
|
let newY = y;
|
||||||
if (x > 1) newX = 1; // Prevent moving from more than one tile
|
if (x > 1) {
|
||||||
if (x < -1) newX = -1;
|
newX = 1;
|
||||||
if (y > 1) newY = 1;
|
} // Prevent moving from more than one tile
|
||||||
if (y < -1) newY = -1;
|
if (x < -1) {
|
||||||
if (x !== 0 && y !== 0) newY = 0; // Prevent diagonal movement
|
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.removeFromGrid(grid);
|
||||||
this.currentShape.move(newX, newY);
|
this.currentShape.move(newX, newY);
|
||||||
const isValid = this.isPositionValid(grid, width, height);
|
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;
|
const shouldFreeze = !isValid && newY !== 0;
|
||||||
this.toGrid(grid, false);
|
this.toGrid(grid, false);
|
||||||
if (shouldFreeze) freezeCallback();
|
if (shouldFreeze) {
|
||||||
|
freezeCallback();
|
||||||
|
}
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,6 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to manage game score
|
* 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
|
* @param numberRemoved The number of lines removed at the same time
|
||||||
*/
|
*/
|
||||||
addLinesRemovedPoints(numberRemoved: number) {
|
addLinesRemovedPoints(numberRemoved: number) {
|
||||||
if (numberRemoved < 1 || numberRemoved > 4) return;
|
if (numberRemoved < 1 || numberRemoved > 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.#score +=
|
this.#score +=
|
||||||
this.#scoreLinesModifier[numberRemoved - 1] * (this.#level + 1);
|
this.#scoreLinesModifier[numberRemoved - 1] * (this.#level + 1);
|
||||||
switch (numberRemoved) {
|
switch (numberRemoved) {
|
|
@ -17,8 +17,6 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {View} from 'react-native';
|
import {View} from 'react-native';
|
||||||
import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
|
import {Caption, IconButton, Text, withTheme} from 'react-native-paper';
|
||||||
|
@ -32,27 +30,26 @@ import Preview from '../components/Preview';
|
||||||
import MaterialHeaderButtons, {
|
import MaterialHeaderButtons, {
|
||||||
Item,
|
Item,
|
||||||
} from '../../../components/Overrides/CustomHeaderButton';
|
} from '../../../components/Overrides/CustomHeaderButton';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
|
import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
|
||||||
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp<any>;
|
||||||
route: {params: {highScore: number}},
|
route: {params: {highScore: number}};
|
||||||
theme: CustomThemeType,
|
theme: ReactNativePaper.Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateType = {
|
type StateType = {
|
||||||
grid: GridType,
|
grid: GridType;
|
||||||
gameTime: number,
|
gameTime: number;
|
||||||
gameScore: number,
|
gameScore: number;
|
||||||
gameLevel: number,
|
gameLevel: number;
|
||||||
|
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean;
|
||||||
dialogTitle: string,
|
dialogTitle: string;
|
||||||
dialogMessage: string,
|
dialogMessage: string;
|
||||||
dialogButtons: Array<OptionsDialogButtonType>,
|
dialogButtons: Array<OptionsDialogButtonType>;
|
||||||
onDialogDismiss: () => void,
|
onDialogDismiss: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameMainScreen extends React.Component<PropsType, StateType> {
|
class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
|
@ -62,11 +59,13 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
date.setMinutes(0);
|
date.setMinutes(0);
|
||||||
date.setSeconds(seconds);
|
date.setSeconds(seconds);
|
||||||
let format;
|
let format;
|
||||||
if (date.getHours())
|
if (date.getHours()) {
|
||||||
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
else if (date.getMinutes())
|
} else if (date.getMinutes()) {
|
||||||
format = `${date.getMinutes()}:${date.getSeconds()}`;
|
format = `${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
else format = date.getSeconds().toString();
|
} else {
|
||||||
|
format = date.getSeconds().toString();
|
||||||
|
}
|
||||||
return format;
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +75,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.highScore = null;
|
||||||
this.logic = new GameLogic(20, 10, props.theme);
|
this.logic = new GameLogic(20, 10, props.theme);
|
||||||
this.state = {
|
this.state = {
|
||||||
grid: this.logic.getCurrentGrid(),
|
grid: this.logic.getCurrentGrid(),
|
||||||
|
@ -88,8 +88,9 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
dialogButtons: [],
|
dialogButtons: [],
|
||||||
onDialogDismiss: () => {},
|
onDialogDismiss: () => {},
|
||||||
};
|
};
|
||||||
if (props.route.params != null)
|
if (props.route.params != null) {
|
||||||
this.highScore = props.route.params.highScore;
|
this.highScore = props.route.params.highScore;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -104,7 +105,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
this.logic.endGame(true);
|
this.logic.endGame(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRightButton = (): React.Node => {
|
getRightButton = () => {
|
||||||
return (
|
return (
|
||||||
<MaterialHeaderButtons>
|
<MaterialHeaderButtons>
|
||||||
<Item title="pause" iconName="pause" onPress={this.togglePause} />
|
<Item title="pause" iconName="pause" onPress={this.togglePause} />
|
||||||
|
@ -136,15 +137,16 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
gameTime: time,
|
gameTime: time,
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
});
|
});
|
||||||
if (!isRestart)
|
if (!isRestart) {
|
||||||
props.navigation.replace('game-start', {
|
props.navigation.replace('game-start', {
|
||||||
score: state.gameScore,
|
score: state.gameScore,
|
||||||
level: state.gameLevel,
|
level: state.gameLevel,
|
||||||
time: state.gameTime,
|
time: state.gameTime,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getStatusIcons(): React.Node {
|
getStatusIcons() {
|
||||||
const {props, state} = this;
|
const {props, state} = this;
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -219,7 +221,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getScoreIcon(): React.Node {
|
getScoreIcon() {
|
||||||
const {props, state} = this;
|
const {props, state} = this;
|
||||||
const highScore =
|
const highScore =
|
||||||
this.highScore == null || state.gameScore > this.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;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -353,8 +355,8 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
updateGridScore = (newGrid: GridType, score?: number) => {
|
updateGridScore = (newGrid: GridType, score?: number) => {
|
||||||
this.setState((prevState: StateType): {
|
this.setState((prevState: StateType): {
|
||||||
grid: GridType,
|
grid: GridType;
|
||||||
gameScore: number,
|
gameScore: number;
|
||||||
} => ({
|
} => ({
|
||||||
grid: newGrid,
|
grid: newGrid,
|
||||||
gameScore: score != null ? score : prevState.gameScore,
|
gameScore: score != null ? score : prevState.gameScore,
|
||||||
|
@ -363,7 +365,9 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
|
|
||||||
togglePause = () => {
|
togglePause = () => {
|
||||||
this.logic.togglePause();
|
this.logic.togglePause();
|
||||||
if (this.logic.isGamePaused()) this.showPausePopup();
|
if (this.logic.isGamePaused()) {
|
||||||
|
this.showPausePopup();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
showPausePopup = () => {
|
showPausePopup = () => {
|
||||||
|
@ -415,7 +419,7 @@ class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
||||||
};
|
};
|
||||||
|
|
||||||
render(): React.Node {
|
render() {
|
||||||
const {props, state} = this;
|
const {props, state} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
|
@ -17,8 +17,6 @@
|
||||||
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @flow
|
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {StackNavigationProp} from '@react-navigation/stack';
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import {
|
import {
|
||||||
|
@ -35,7 +33,6 @@ import i18n from 'i18n-js';
|
||||||
import * as Animatable from 'react-native-animatable';
|
import * as Animatable from 'react-native-animatable';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
|
||||||
import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
|
import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
|
||||||
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||||
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||||
|
@ -47,17 +44,17 @@ import SpeechArrow from '../../../components/Mascot/SpeechArrow';
|
||||||
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
||||||
type GameStatsType = {
|
type GameStatsType = {
|
||||||
score: number,
|
score: number;
|
||||||
level: number,
|
level: number;
|
||||||
time: number,
|
time: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
type PropsType = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp<any>;
|
||||||
route: {
|
route: {
|
||||||
params: GameStatsType,
|
params: GameStatsType;
|
||||||
},
|
};
|
||||||
theme: CustomThemeType,
|
theme: ReactNativePaper.Theme;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GameStartScreen extends React.Component<PropsType> {
|
class GameStartScreen extends React.Component<PropsType> {
|
||||||
|
@ -65,21 +62,24 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
|
|
||||||
scores: Array<number>;
|
scores: Array<number>;
|
||||||
|
|
||||||
gameStats: GameStatsType | null;
|
gameStats?: GameStatsType;
|
||||||
|
|
||||||
isHighScore: boolean;
|
isHighScore: boolean;
|
||||||
|
|
||||||
constructor(props: PropsType) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.isHighScore = false;
|
||||||
this.gridManager = new GridManager(4, 4, props.theme);
|
this.gridManager = new GridManager(4, 4, props.theme);
|
||||||
this.scores = AsyncStorageManager.getObject(
|
this.scores = AsyncStorageManager.getObject(
|
||||||
AsyncStorageManager.PREFERENCES.gameScores.key,
|
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||||
);
|
);
|
||||||
this.scores.sort((a: number, b: number): number => b - a);
|
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 {theme} = this.props;
|
||||||
const gridList = [];
|
const gridList = [];
|
||||||
for (let i = 0; i < 18; i += 1) {
|
for (let i = 0; i < 18; i += 1) {
|
||||||
|
@ -94,7 +94,7 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '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 size = 10 + Math.floor(Math.random() * 30);
|
||||||
const top = Math.floor(Math.random() * 100);
|
const top = Math.floor(Math.random() * 100);
|
||||||
const rot = Math.floor(Math.random() * 360);
|
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;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
|
@ -141,8 +141,8 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
animated={this.isHighScore}
|
animated={this.isHighScore}
|
||||||
style={{
|
style={{
|
||||||
width: this.isHighScore ? '50%' : '30%',
|
width: this.isHighScore ? '50%' : '30%',
|
||||||
marginLeft: this.isHighScore ? 'auto' : null,
|
marginLeft: this.isHighScore ? 'auto' : undefined,
|
||||||
marginRight: this.isHighScore ? 'auto' : null,
|
marginRight: this.isHighScore ? 'auto' : undefined,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<SpeechArrow
|
<SpeechArrow
|
||||||
|
@ -235,7 +235,7 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWelcomeText(): React.Node {
|
getWelcomeText() {
|
||||||
const {props} = this;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View>
|
<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;
|
const {props} = this;
|
||||||
let icon = 'podium-gold';
|
let icon = 'podium-gold';
|
||||||
let color = props.theme.colors.gameGold;
|
let color = props.theme.colors.gameGold;
|
||||||
|
@ -338,7 +338,7 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: place === 1 ? 'bold' : null,
|
fontWeight: place === 1 ? 'bold' : undefined,
|
||||||
fontSize,
|
fontSize,
|
||||||
}}>
|
}}>
|
||||||
{score}
|
{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 gold = this.scores.length > 0 ? this.scores[0] : '-';
|
||||||
const silver = this.scores.length > 1 ? this.scores[1] : '-';
|
const silver = this.scores.length > 1 ? this.scores[1] : '-';
|
||||||
const bronze = this.scores.length > 2 ? this.scores[2] : '-';
|
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;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
|
@ -415,7 +415,9 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.scores.length > 3) this.scores.splice(3, 1);
|
if (this.scores.length > 3) {
|
||||||
|
this.scores.splice(3, 1);
|
||||||
|
}
|
||||||
AsyncStorageManager.set(
|
AsyncStorageManager.set(
|
||||||
AsyncStorageManager.PREFERENCES.gameScores.key,
|
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||||
this.scores,
|
this.scores,
|
||||||
|
@ -423,7 +425,7 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): React.Node {
|
render() {
|
||||||
const {props} = this;
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
|
@ -444,7 +446,6 @@ class GameStartScreen extends React.Component<PropsType> {
|
||||||
message={i18n.t('screens.game.mascotDialog.message')}
|
message={i18n.t('screens.game.mascotDialog.message')}
|
||||||
icon="gamepad-variant"
|
icon="gamepad-variant"
|
||||||
buttons={{
|
buttons={{
|
||||||
action: null,
|
|
||||||
cancel: {
|
cancel: {
|
||||||
message: i18n.t('screens.game.mascotDialog.button'),
|
message: i18n.t('screens.game.mascotDialog.button'),
|
||||||
icon: 'check',
|
icon: 'check',
|
Loading…
Reference in a new issue