forked from vergnet/application-amicale
Improve Game files to match linter
This commit is contained in:
parent
569e659779
commit
cbe3777957
16 changed files with 1750 additions and 1610 deletions
|
@ -1,13 +1,13 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
|
||||||
export type Coordinates = {
|
export type CoordinatesType = {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
}
|
};
|
||||||
|
|
||||||
type Shape = Array<Array<number>>;
|
export type ShapeType = Array<Array<number>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class used to represent a BaseShape.
|
* Abstract class used to represent a BaseShape.
|
||||||
|
@ -15,16 +15,18 @@ type Shape = Array<Array<number>>;
|
||||||
* and in methods to implement
|
* and in methods to implement
|
||||||
*/
|
*/
|
||||||
export default class BaseShape {
|
export default class BaseShape {
|
||||||
|
#currentShape: ShapeType;
|
||||||
|
|
||||||
#currentShape: Shape;
|
|
||||||
#rotation: number;
|
#rotation: number;
|
||||||
position: Coordinates;
|
|
||||||
theme: CustomTheme;
|
position: CoordinatesType;
|
||||||
|
|
||||||
|
theme: CustomThemeType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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: CustomTheme) {
|
constructor(theme: CustomThemeType) {
|
||||||
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;
|
||||||
|
@ -37,6 +39,7 @@ 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");
|
||||||
}
|
}
|
||||||
|
@ -47,14 +50,15 @@ export default class BaseShape {
|
||||||
*
|
*
|
||||||
* Used by tests to read private fields
|
* Used by tests to read private fields
|
||||||
*/
|
*/
|
||||||
getShapes(): Array<Shape> {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
throw new Error("Method 'getShapes()' must be implemented");
|
throw new Error("Method 'getShapes()' must be implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets this object's current shape.
|
* Gets this object's current shape.
|
||||||
*/
|
*/
|
||||||
getCurrentShape(): Shape {
|
getCurrentShape(): ShapeType {
|
||||||
return this.#currentShape;
|
return this.#currentShape;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,17 +67,20 @@ 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<CoordinatesType>} This object cells coordinates
|
||||||
*/
|
*/
|
||||||
getCellsCoordinates(isAbsolute: boolean): Array<Coordinates> {
|
getCellsCoordinates(isAbsolute: boolean): Array<CoordinatesType> {
|
||||||
let coordinates = [];
|
const coordinates = [];
|
||||||
for (let row = 0; row < this.#currentShape.length; row++) {
|
for (let row = 0; row < this.#currentShape.length; row += 1) {
|
||||||
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
for (let col = 0; col < this.#currentShape[row].length; col += 1) {
|
||||||
if (this.#currentShape[row][col] === 1)
|
if (this.#currentShape[row][col] === 1) {
|
||||||
if (isAbsolute)
|
if (isAbsolute) {
|
||||||
coordinates.push({x: this.position.x + col, y: this.position.y + row});
|
coordinates.push({
|
||||||
else
|
x: this.position.x + col,
|
||||||
coordinates.push({x: col, y: row});
|
y: this.position.y + row,
|
||||||
|
});
|
||||||
|
} else coordinates.push({x: col, y: row});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return coordinates;
|
return coordinates;
|
||||||
|
@ -85,14 +92,10 @@ export default class BaseShape {
|
||||||
* @param isForward Should we rotate clockwise?
|
* @param isForward Should we rotate clockwise?
|
||||||
*/
|
*/
|
||||||
rotate(isForward: boolean) {
|
rotate(isForward: boolean) {
|
||||||
if (isForward)
|
if (isForward) this.#rotation += 1;
|
||||||
this.#rotation++;
|
else this.#rotation -= 1;
|
||||||
else
|
if (this.#rotation > 3) this.#rotation = 0;
|
||||||
this.#rotation--;
|
else if (this.#rotation < 0) this.#rotation = 3;
|
||||||
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];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,5 +109,4 @@ export default class BaseShape {
|
||||||
this.position.x += x;
|
this.position.x += x;
|
||||||
this.position.y += y;
|
this.position.y += y;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeI extends BaseShape {
|
export default class ShapeI extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeI extends BaseShape {
|
||||||
return this.theme.colors.tetrisI;
|
return this.theme.colors.tetrisI;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[0, 0, 0, 0],
|
[0, 0, 0, 0],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeJ extends BaseShape {
|
export default class ShapeJ extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeJ extends BaseShape {
|
||||||
return this.theme.colors.tetrisJ;
|
return this.theme.colors.tetrisJ;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[1, 0, 0],
|
[1, 0, 0],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeL extends BaseShape {
|
export default class ShapeL extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeL extends BaseShape {
|
||||||
return this.theme.colors.tetrisL;
|
return this.theme.colors.tetrisL;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[0, 0, 1],
|
[0, 0, 1],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeO extends BaseShape {
|
export default class ShapeO extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 4;
|
this.position.x = 4;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeO extends BaseShape {
|
||||||
return this.theme.colors.tetrisO;
|
return this.theme.colors.tetrisO;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[1, 1],
|
[1, 1],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeS extends BaseShape {
|
export default class ShapeS extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeS extends BaseShape {
|
||||||
return this.theme.colors.tetrisS;
|
return this.theme.colors.tetrisS;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[0, 1, 1],
|
[0, 1, 1],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeT extends BaseShape {
|
export default class ShapeT extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeT extends BaseShape {
|
||||||
return this.theme.colors.tetrisT;
|
return this.theme.colors.tetrisT;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[0, 1, 0],
|
[0, 1, 0],
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import BaseShape from "./BaseShape";
|
import BaseShape from './BaseShape';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {ShapeType} from './BaseShape';
|
||||||
|
|
||||||
export default class ShapeZ extends BaseShape {
|
export default class ShapeZ extends BaseShape {
|
||||||
|
constructor(theme: CustomThemeType) {
|
||||||
constructor(theme: CustomTheme) {
|
|
||||||
super(theme);
|
super(theme);
|
||||||
this.position.x = 3;
|
this.position.x = 3;
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,8 @@ export default class ShapeZ extends BaseShape {
|
||||||
return this.theme.colors.tetrisZ;
|
return this.theme.colors.tetrisZ;
|
||||||
}
|
}
|
||||||
|
|
||||||
getShapes() {
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
getShapes(): Array<ShapeType> {
|
||||||
return [
|
return [
|
||||||
[
|
[
|
||||||
[1, 1, 0],
|
[1, 1, 0],
|
||||||
|
|
|
@ -3,19 +3,17 @@
|
||||||
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};
|
export type CellType = {color: string, isEmpty: boolean, key: string};
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
cell: Cell,
|
cell: CellType,
|
||||||
theme: CustomTheme,
|
};
|
||||||
}
|
|
||||||
|
|
||||||
class CellComponent extends React.PureComponent<Props> {
|
class CellComponent extends React.PureComponent<PropsType> {
|
||||||
|
render(): React.Node {
|
||||||
render() {
|
const {props} = this;
|
||||||
const item = this.props.cell;
|
const item = props.cell;
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
@ -29,8 +27,6 @@ class CellComponent extends React.PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(CellComponent);
|
export default withTheme(CellComponent);
|
||||||
|
|
|
@ -3,51 +3,50 @@
|
||||||
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 {Cell} from "./CellComponent";
|
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||||
import CellComponent from "./CellComponent";
|
import type {CellType} from './CellComponent';
|
||||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
import CellComponent from './CellComponent';
|
||||||
|
|
||||||
export type Grid = Array<Array<CellComponent>>;
|
export type GridType = Array<Array<CellComponent>>;
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
grid: Array<Array<Object>>,
|
grid: Array<Array<CellType>>,
|
||||||
height: number,
|
height: number,
|
||||||
width: number,
|
width: number,
|
||||||
style: ViewStyle,
|
style: ViewStyle,
|
||||||
}
|
};
|
||||||
|
|
||||||
class GridComponent extends React.Component<Props> {
|
class GridComponent extends React.Component<PropsType> {
|
||||||
|
getRow(rowNumber: number): React.Node {
|
||||||
getRow(rowNumber: number) {
|
const {grid} = this.props;
|
||||||
let cells = this.props.grid[rowNumber].map(this.getCellRender);
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View style={{flexDirection: 'row'}} key={rowNumber.toString()}>
|
||||||
style={{flexDirection: 'row',}}
|
{grid[rowNumber].map(this.getCellRender)}
|
||||||
key={rowNumber.toString()}
|
|
||||||
>
|
|
||||||
{cells}
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCellRender = (item: Cell) => {
|
getCellRender = (item: CellType): React.Node => {
|
||||||
return <CellComponent cell={item} key={item.key} />;
|
return <CellComponent cell={item} key={item.key} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
getGrid() {
|
getGrid(): React.Node {
|
||||||
let rows = [];
|
const {height} = this.props;
|
||||||
for (let i = 0; i < this.props.height; i++) {
|
const rows = [];
|
||||||
|
for (let i = 0; i < height; i += 1) {
|
||||||
rows.push(this.getRow(i));
|
rows.push(this.getRow(i));
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render(): React.Node {
|
||||||
|
const {style, width, height} = this.props;
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
aspectRatio: this.props.width / this.props.height,
|
style={{
|
||||||
|
aspectRatio: width / height,
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
...this.props.style
|
...style,
|
||||||
}}>
|
}}>
|
||||||
{this.getGrid()}
|
{this.getGrid()}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -3,27 +3,28 @@
|
||||||
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 {Grid} from "./GridComponent";
|
import type {ViewStyle} from 'react-native/Libraries/StyleSheet/StyleSheet';
|
||||||
import GridComponent from "./GridComponent";
|
import type {GridType} from './GridComponent';
|
||||||
import type {ViewStyle} from "react-native/Libraries/StyleSheet/StyleSheet";
|
import GridComponent from './GridComponent';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
items: Array<Grid>,
|
items: Array<GridType>,
|
||||||
style: ViewStyle
|
style: ViewStyle,
|
||||||
}
|
};
|
||||||
|
|
||||||
class Preview extends React.PureComponent<Props> {
|
class Preview extends React.PureComponent<PropsType> {
|
||||||
|
getGrids(): React.Node {
|
||||||
getGrids() {
|
const {items} = this.props;
|
||||||
let grids = [];
|
const grids = [];
|
||||||
for (let i = 0; i < this.props.items.length; i++) {
|
items.forEach((item: GridType, index: number) => {
|
||||||
grids.push(this.getGridRender(this.props.items[i], i));
|
grids.push(Preview.getGridRender(item, index));
|
||||||
}
|
});
|
||||||
return grids;
|
return grids;
|
||||||
}
|
}
|
||||||
|
|
||||||
getGridRender(item: Grid, index: number) {
|
static getGridRender(item: GridType, index: number): React.Node {
|
||||||
return <GridComponent
|
return (
|
||||||
|
<GridComponent
|
||||||
width={item[0].length}
|
width={item[0].length}
|
||||||
height={item.length}
|
height={item.length}
|
||||||
grid={item}
|
grid={item}
|
||||||
|
@ -33,21 +34,17 @@ class Preview extends React.PureComponent<Props> {
|
||||||
marginBottom: 5,
|
marginBottom: 5,
|
||||||
}}
|
}}
|
||||||
key={index.toString()}
|
key={index.toString()}
|
||||||
/>;
|
/>
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (this.props.items.length > 0) {
|
|
||||||
return (
|
|
||||||
<View style={this.props.style}>
|
|
||||||
{this.getGrids()}
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
} else
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {style, items} = this.props;
|
||||||
|
if (items.length > 0) {
|
||||||
|
return <View style={style}>{this.getGrids()}</View>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(Preview);
|
export default withTheme(Preview);
|
||||||
|
|
|
@ -1,243 +1,318 @@
|
||||||
// @flow
|
// @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 {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {GridType} from '../components/GridComponent';
|
||||||
|
|
||||||
|
export type TickCallbackType = (
|
||||||
|
score: number,
|
||||||
|
level: number,
|
||||||
|
grid: GridType,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type ClockCallbackType = (time: number) => void;
|
||||||
|
|
||||||
|
export type EndCallbackType = (
|
||||||
|
time: number,
|
||||||
|
score: number,
|
||||||
|
isRestart: boolean,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
export type MovementCallbackType = (grid: GridType, score?: number) => void;
|
||||||
|
|
||||||
export default class GameLogic {
|
export default class GameLogic {
|
||||||
|
static levelTicks = [1000, 800, 600, 400, 300, 200, 150, 100];
|
||||||
|
|
||||||
static levelTicks = [
|
scoreManager: ScoreManager;
|
||||||
1000,
|
|
||||||
800,
|
|
||||||
600,
|
|
||||||
400,
|
|
||||||
300,
|
|
||||||
200,
|
|
||||||
150,
|
|
||||||
100,
|
|
||||||
];
|
|
||||||
|
|
||||||
#scoreManager: ScoreManager;
|
gridManager: GridManager;
|
||||||
#gridManager: GridManager;
|
|
||||||
|
|
||||||
#height: number;
|
height: number;
|
||||||
#width: number;
|
|
||||||
|
|
||||||
#gameRunning: boolean;
|
width: number;
|
||||||
#gamePaused: boolean;
|
|
||||||
#gameTime: number;
|
|
||||||
|
|
||||||
#currentObject: Piece;
|
gameRunning: boolean;
|
||||||
|
|
||||||
#gameTick: number;
|
gamePaused: boolean;
|
||||||
#gameTickInterval: IntervalID;
|
|
||||||
#gameTimeInterval: IntervalID;
|
|
||||||
|
|
||||||
#pressInInterval: TimeoutID;
|
gameTime: number;
|
||||||
#isPressedIn: boolean;
|
|
||||||
#autoRepeatActivationDelay: number;
|
|
||||||
#autoRepeatDelay: number;
|
|
||||||
|
|
||||||
#nextPieces: Array<Piece>;
|
currentObject: Piece;
|
||||||
#nextPiecesCount: number;
|
|
||||||
|
|
||||||
#onTick: Function;
|
gameTick: number;
|
||||||
#onClock: Function;
|
|
||||||
endCallback: Function;
|
|
||||||
|
|
||||||
#theme: CustomTheme;
|
gameTickInterval: IntervalID;
|
||||||
|
|
||||||
constructor(height: number, width: number, theme: CustomTheme) {
|
gameTimeInterval: IntervalID;
|
||||||
this.#height = height;
|
|
||||||
this.#width = width;
|
pressInInterval: TimeoutID;
|
||||||
this.#gameRunning = false;
|
|
||||||
this.#gamePaused = false;
|
isPressedIn: boolean;
|
||||||
this.#theme = theme;
|
|
||||||
this.#autoRepeatActivationDelay = 300;
|
autoRepeatActivationDelay: number;
|
||||||
this.#autoRepeatDelay = 50;
|
|
||||||
this.#nextPieces = [];
|
autoRepeatDelay: number;
|
||||||
this.#nextPiecesCount = 3;
|
|
||||||
this.#scoreManager = new ScoreManager();
|
nextPieces: Array<Piece>;
|
||||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
|
|
||||||
|
nextPiecesCount: number;
|
||||||
|
|
||||||
|
tickCallback: TickCallbackType;
|
||||||
|
|
||||||
|
clockCallback: ClockCallbackType;
|
||||||
|
|
||||||
|
endCallback: EndCallbackType;
|
||||||
|
|
||||||
|
theme: CustomThemeType;
|
||||||
|
|
||||||
|
constructor(height: number, width: number, theme: CustomThemeType) {
|
||||||
|
this.height = height;
|
||||||
|
this.width = width;
|
||||||
|
this.gameRunning = false;
|
||||||
|
this.gamePaused = false;
|
||||||
|
this.theme = theme;
|
||||||
|
this.autoRepeatActivationDelay = 300;
|
||||||
|
this.autoRepeatDelay = 50;
|
||||||
|
this.nextPieces = [];
|
||||||
|
this.nextPiecesCount = 3;
|
||||||
|
this.scoreManager = new ScoreManager();
|
||||||
|
this.gridManager = new GridManager(
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
this.theme,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeight(): number {
|
getHeight(): number {
|
||||||
return this.#height;
|
return this.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth(): number {
|
getWidth(): number {
|
||||||
return this.#width;
|
return this.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentGrid() {
|
getCurrentGrid(): GridType {
|
||||||
return this.#gridManager.getCurrentGrid();
|
return this.gridManager.getCurrentGrid();
|
||||||
}
|
|
||||||
|
|
||||||
isGameRunning(): boolean {
|
|
||||||
return this.#gameRunning;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isGamePaused(): boolean {
|
isGamePaused(): boolean {
|
||||||
return this.#gamePaused;
|
return this.gamePaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
onFreeze() {
|
onFreeze = () => {
|
||||||
this.#gridManager.freezeTetromino(this.#currentObject, this.#scoreManager);
|
this.gridManager.freezeTetromino(this.currentObject, this.scoreManager);
|
||||||
this.createTetromino();
|
this.createTetromino();
|
||||||
}
|
};
|
||||||
|
|
||||||
setNewGameTick(level: number) {
|
setNewGameTick(level: number) {
|
||||||
if (level >= GameLogic.levelTicks.length)
|
if (level >= GameLogic.levelTicks.length) return;
|
||||||
return;
|
this.gameTick = GameLogic.levelTicks[level];
|
||||||
this.#gameTick = GameLogic.levelTicks[level];
|
this.stopTick();
|
||||||
clearInterval(this.#gameTickInterval);
|
this.startTick();
|
||||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onTick(callback: Function) {
|
startClock() {
|
||||||
this.#currentObject.tryMove(0, 1,
|
this.gameTimeInterval = setInterval(() => {
|
||||||
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
this.onClock(this.clockCallback);
|
||||||
() => this.onFreeze());
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
startTick() {
|
||||||
|
this.gameTickInterval = setInterval(() => {
|
||||||
|
this.onTick(this.tickCallback);
|
||||||
|
}, this.gameTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopClock() {
|
||||||
|
clearInterval(this.gameTimeInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTick() {
|
||||||
|
clearInterval(this.gameTickInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopGameTime() {
|
||||||
|
this.stopClock();
|
||||||
|
this.stopTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
startGameTime() {
|
||||||
|
this.startClock();
|
||||||
|
this.startTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
onTick(callback: TickCallbackType) {
|
||||||
|
this.currentObject.tryMove(
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
this.gridManager.getCurrentGrid(),
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
this.onFreeze,
|
||||||
|
);
|
||||||
callback(
|
callback(
|
||||||
this.#scoreManager.getScore(),
|
this.scoreManager.getScore(),
|
||||||
this.#scoreManager.getLevel(),
|
this.scoreManager.getLevel(),
|
||||||
this.#gridManager.getCurrentGrid());
|
this.gridManager.getCurrentGrid(),
|
||||||
if (this.#scoreManager.canLevelUp())
|
);
|
||||||
this.setNewGameTick(this.#scoreManager.getLevel());
|
if (this.scoreManager.canLevelUp())
|
||||||
|
this.setNewGameTick(this.scoreManager.getLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
onClock(callback: Function) {
|
onClock(callback: ClockCallbackType) {
|
||||||
this.#gameTime++;
|
this.gameTime += 1;
|
||||||
callback(this.#gameTime);
|
callback(this.gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
canUseInput() {
|
canUseInput(): boolean {
|
||||||
return this.#gameRunning && !this.#gamePaused
|
return this.gameRunning && !this.gamePaused;
|
||||||
}
|
}
|
||||||
|
|
||||||
rightPressed(callback: Function) {
|
rightPressed(callback: MovementCallbackType) {
|
||||||
this.#isPressedIn = true;
|
this.isPressedIn = true;
|
||||||
this.movePressedRepeat(true, callback, 1, 0);
|
this.movePressedRepeat(true, callback, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
leftPressedIn(callback: Function) {
|
leftPressedIn(callback: MovementCallbackType) {
|
||||||
this.#isPressedIn = true;
|
this.isPressedIn = true;
|
||||||
this.movePressedRepeat(true, callback, -1, 0);
|
this.movePressedRepeat(true, callback, -1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
downPressedIn(callback: Function) {
|
downPressedIn(callback: MovementCallbackType) {
|
||||||
this.#isPressedIn = true;
|
this.isPressedIn = true;
|
||||||
this.movePressedRepeat(true, callback, 0, 1);
|
this.movePressedRepeat(true, callback, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
|
movePressedRepeat(
|
||||||
if (!this.canUseInput() || !this.#isPressedIn)
|
isInitial: boolean,
|
||||||
return;
|
callback: MovementCallbackType,
|
||||||
const moved = this.#currentObject.tryMove(x, y,
|
x: number,
|
||||||
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
y: number,
|
||||||
() => this.onFreeze());
|
) {
|
||||||
|
if (!this.canUseInput() || !this.isPressedIn) return;
|
||||||
|
const moved = this.currentObject.tryMove(
|
||||||
|
x,
|
||||||
|
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();
|
||||||
callback(this.#gridManager.getCurrentGrid(), this.#scoreManager.getScore());
|
callback(
|
||||||
} else
|
this.gridManager.getCurrentGrid(),
|
||||||
callback(this.#gridManager.getCurrentGrid());
|
this.scoreManager.getScore(),
|
||||||
|
);
|
||||||
|
} else callback(this.gridManager.getCurrentGrid());
|
||||||
}
|
}
|
||||||
this.#pressInInterval = setTimeout(() =>
|
this.pressInInterval = setTimeout(
|
||||||
this.movePressedRepeat(false, callback, x, y),
|
() => {
|
||||||
isInitial ? this.#autoRepeatActivationDelay : this.#autoRepeatDelay
|
this.movePressedRepeat(false, callback, x, y);
|
||||||
|
},
|
||||||
|
isInitial ? this.autoRepeatActivationDelay : this.autoRepeatDelay,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pressedOut() {
|
pressedOut() {
|
||||||
this.#isPressedIn = false;
|
this.isPressedIn = false;
|
||||||
clearTimeout(this.#pressInInterval);
|
clearTimeout(this.pressInInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotatePressed(callback: Function) {
|
rotatePressed(callback: MovementCallbackType) {
|
||||||
if (!this.canUseInput())
|
if (!this.canUseInput()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (this.#currentObject.tryRotate(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
if (
|
||||||
callback(this.#gridManager.getCurrentGrid());
|
this.currentObject.tryRotate(
|
||||||
|
this.gridManager.getCurrentGrid(),
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
callback(this.gridManager.getCurrentGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextPiecesPreviews() {
|
getNextPiecesPreviews(): Array<GridType> {
|
||||||
let finalArray = [];
|
const finalArray = [];
|
||||||
for (let i = 0; i < this.#nextPieces.length; i++) {
|
for (let i = 0; i < this.nextPieces.length; i += 1) {
|
||||||
const gridSize = this.#nextPieces[i].getCurrentShape().getCurrentShape()[0].length;
|
const gridSize = this.nextPieces[i].getCurrentShape().getCurrentShape()[0]
|
||||||
finalArray.push(this.#gridManager.getEmptyGrid(gridSize, gridSize));
|
.length;
|
||||||
this.#nextPieces[i].toGrid(finalArray[i], true);
|
finalArray.push(this.gridManager.getEmptyGrid(gridSize, gridSize));
|
||||||
|
this.nextPieces[i].toGrid(finalArray[i], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalArray;
|
return finalArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
recoverNextPiece() {
|
recoverNextPiece() {
|
||||||
this.#currentObject = this.#nextPieces.shift();
|
this.currentObject = this.nextPieces.shift();
|
||||||
this.generateNextPieces();
|
this.generateNextPieces();
|
||||||
}
|
}
|
||||||
|
|
||||||
generateNextPieces() {
|
generateNextPieces() {
|
||||||
while (this.#nextPieces.length < this.#nextPiecesCount) {
|
while (this.nextPieces.length < this.nextPiecesCount) {
|
||||||
this.#nextPieces.push(new Piece(this.#theme));
|
this.nextPieces.push(new Piece(this.theme));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createTetromino() {
|
createTetromino() {
|
||||||
this.pressedOut();
|
this.pressedOut();
|
||||||
this.recoverNextPiece();
|
this.recoverNextPiece();
|
||||||
if (!this.#currentObject.isPositionValid(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
if (
|
||||||
|
!this.currentObject.isPositionValid(
|
||||||
|
this.gridManager.getCurrentGrid(),
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
)
|
||||||
|
)
|
||||||
this.endGame(false);
|
this.endGame(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePause() {
|
togglePause() {
|
||||||
if (!this.#gameRunning)
|
if (!this.gameRunning) return;
|
||||||
return;
|
this.gamePaused = !this.gamePaused;
|
||||||
this.#gamePaused = !this.#gamePaused;
|
if (this.gamePaused) this.stopGameTime();
|
||||||
if (this.#gamePaused) {
|
else this.startGameTime();
|
||||||
clearInterval(this.#gameTickInterval);
|
|
||||||
clearInterval(this.#gameTimeInterval);
|
|
||||||
} else {
|
|
||||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
|
||||||
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stopGame() {
|
|
||||||
this.#gameRunning = false;
|
|
||||||
this.#gamePaused = false;
|
|
||||||
clearInterval(this.#gameTickInterval);
|
|
||||||
clearInterval(this.#gameTimeInterval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endGame(isRestart: boolean) {
|
endGame(isRestart: boolean) {
|
||||||
this.stopGame();
|
this.gameRunning = false;
|
||||||
this.endCallback(this.#gameTime, this.#scoreManager.getScore(), isRestart);
|
this.gamePaused = false;
|
||||||
|
this.stopGameTime();
|
||||||
|
this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
|
startGame(
|
||||||
if (this.#gameRunning)
|
tickCallback: TickCallbackType,
|
||||||
this.endGame(true);
|
clockCallback: ClockCallbackType,
|
||||||
this.#gameRunning = true;
|
endCallback: EndCallbackType,
|
||||||
this.#gamePaused = false;
|
) {
|
||||||
this.#gameTime = 0;
|
if (this.gameRunning) this.endGame(true);
|
||||||
this.#scoreManager = new ScoreManager();
|
this.gameRunning = true;
|
||||||
this.#gameTick = GameLogic.levelTicks[this.#scoreManager.getLevel()];
|
this.gamePaused = false;
|
||||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#theme);
|
this.gameTime = 0;
|
||||||
this.#nextPieces = [];
|
this.scoreManager = new ScoreManager();
|
||||||
|
this.gameTick = GameLogic.levelTicks[this.scoreManager.getLevel()];
|
||||||
|
this.gridManager = new GridManager(
|
||||||
|
this.getWidth(),
|
||||||
|
this.getHeight(),
|
||||||
|
this.theme,
|
||||||
|
);
|
||||||
|
this.nextPieces = [];
|
||||||
this.generateNextPieces();
|
this.generateNextPieces();
|
||||||
this.createTetromino();
|
this.createTetromino();
|
||||||
tickCallback(
|
tickCallback(
|
||||||
this.#scoreManager.getScore(),
|
this.scoreManager.getScore(),
|
||||||
this.#scoreManager.getLevel(),
|
this.scoreManager.getLevel(),
|
||||||
this.#gridManager.getCurrentGrid());
|
this.gridManager.getCurrentGrid(),
|
||||||
clockCallback(this.#gameTime);
|
);
|
||||||
this.#onTick = this.onTick.bind(this, tickCallback);
|
clockCallback(this.gameTime);
|
||||||
this.#onClock = this.onClock.bind(this, clockCallback);
|
this.startTick();
|
||||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
this.startClock();
|
||||||
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
this.tickCallback = tickCallback;
|
||||||
|
this.clockCallback = clockCallback;
|
||||||
this.endCallback = endCallback;
|
this.endCallback = endCallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import Piece from "./Piece";
|
import Piece from './Piece';
|
||||||
import ScoreManager from "./ScoreManager";
|
import ScoreManager from './ScoreManager';
|
||||||
import type {Coordinates} from '../Shapes/BaseShape';
|
import type {CoordinatesType} from '../Shapes/BaseShape';
|
||||||
import type {Grid} from "../components/GridComponent";
|
import type {GridType} from '../components/GridComponent';
|
||||||
import type {Cell} from "../components/CellComponent";
|
import type {CellType} from '../components/CellComponent';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to manage the game grid
|
* Class used to manage the game grid
|
||||||
*/
|
*/
|
||||||
export default class GridManager {
|
export default class GridManager {
|
||||||
|
#currentGrid: GridType;
|
||||||
|
|
||||||
#currentGrid: Grid;
|
#theme: CustomThemeType;
|
||||||
#theme: CustomTheme;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a grid of the given size
|
* Initializes a grid of the given size
|
||||||
|
@ -22,7 +22,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: CustomTheme) {
|
constructor(width: number, height: number, theme: CustomThemeType) {
|
||||||
this.#theme = theme;
|
this.#theme = theme;
|
||||||
this.#currentGrid = this.getEmptyGrid(height, width);
|
this.#currentGrid = this.getEmptyGrid(height, width);
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,9 @@ export default class GridManager {
|
||||||
/**
|
/**
|
||||||
* Get the current grid
|
* Get the current grid
|
||||||
*
|
*
|
||||||
* @return {Grid} The current grid
|
* @return {GridType} The current grid
|
||||||
*/
|
*/
|
||||||
getCurrentGrid(): Grid {
|
getCurrentGrid(): GridType {
|
||||||
return this.#currentGrid;
|
return this.#currentGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,11 +40,11 @@ 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<CellType>}
|
||||||
*/
|
*/
|
||||||
getEmptyLine(width: number): Array<Cell> {
|
getEmptyLine(width: number): Array<CellType> {
|
||||||
let line = [];
|
const line = [];
|
||||||
for (let col = 0; col < width; col++) {
|
for (let col = 0; col < width; col += 1) {
|
||||||
line.push({
|
line.push({
|
||||||
color: this.#theme.colors.tetrisBackground,
|
color: this.#theme.colors.tetrisBackground,
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
|
@ -59,11 +59,11 @@ 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 {GridType} A new empty grid
|
||||||
*/
|
*/
|
||||||
getEmptyGrid(height: number, width: number): Grid {
|
getEmptyGrid(height: number, width: number): GridType {
|
||||||
let grid = [];
|
const grid = [];
|
||||||
for (let row = 0; row < height; row++) {
|
for (let row = 0; row < height; row += 1) {
|
||||||
grid.push(this.getEmptyLine(width));
|
grid.push(this.getEmptyLine(width));
|
||||||
}
|
}
|
||||||
return grid;
|
return grid;
|
||||||
|
@ -78,7 +78,7 @@ export default class GridManager {
|
||||||
*/
|
*/
|
||||||
clearLines(lines: Array<number>, scoreManager: ScoreManager) {
|
clearLines(lines: Array<number>, scoreManager: ScoreManager) {
|
||||||
lines.sort();
|
lines.sort();
|
||||||
for (let i = 0; i < lines.length; i++) {
|
for (let i = 0; i < lines.length; i += 1) {
|
||||||
this.#currentGrid.splice(lines[i], 1);
|
this.#currentGrid.splice(lines[i], 1);
|
||||||
this.#currentGrid.unshift(this.getEmptyLine(this.#currentGrid[0].length));
|
this.#currentGrid.unshift(this.getEmptyLine(this.#currentGrid[0].length));
|
||||||
}
|
}
|
||||||
|
@ -92,18 +92,17 @@ export default class GridManager {
|
||||||
* @param pos 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(pos: Array<Coordinates>): Array<number> {
|
getLinesToClear(pos: Array<CoordinatesType>): Array<number> {
|
||||||
let rows = [];
|
const rows = [];
|
||||||
for (let i = 0; i < pos.length; i++) {
|
for (let i = 0; i < pos.length; i += 1) {
|
||||||
let isLineFull = true;
|
let isLineFull = true;
|
||||||
for (let col = 0; col < this.#currentGrid[pos[i].y].length; col++) {
|
for (let col = 0; col < this.#currentGrid[pos[i].y].length; col += 1) {
|
||||||
if (this.#currentGrid[pos[i].y][col].isEmpty) {
|
if (this.#currentGrid[pos[i].y][col].isEmpty) {
|
||||||
isLineFull = false;
|
isLineFull = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isLineFull && rows.indexOf(pos[i].y) === -1)
|
if (isLineFull && rows.indexOf(pos[i].y) === -1) rows.push(pos[i].y);
|
||||||
rows.push(pos[i].y);
|
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
@ -115,6 +114,9 @@ export default class GridManager {
|
||||||
* @param scoreManager A reference to the score manager
|
* @param scoreManager A reference to the score manager
|
||||||
*/
|
*/
|
||||||
freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
|
freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
|
||||||
this.clearLines(this.getLinesToClear(currentObject.getCoordinates()), scoreManager);
|
this.clearLines(
|
||||||
|
this.getLinesToClear(currentObject.getCoordinates()),
|
||||||
|
scoreManager,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
* Class used to manage game score
|
* Class used to manage game score
|
||||||
*/
|
*/
|
||||||
export default class ScoreManager {
|
export default class ScoreManager {
|
||||||
|
|
||||||
#scoreLinesModifier = [40, 100, 300, 1200];
|
#scoreLinesModifier = [40, 100, 300, 1200];
|
||||||
|
|
||||||
#score: number;
|
#score: number;
|
||||||
|
|
||||||
#level: number;
|
#level: number;
|
||||||
|
|
||||||
#levelProgression: number;
|
#levelProgression: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +52,7 @@ export default class ScoreManager {
|
||||||
* Increments the score by one
|
* Increments the score by one
|
||||||
*/
|
*/
|
||||||
incrementScore() {
|
incrementScore() {
|
||||||
this.#score++;
|
this.#score += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -63,9 +64,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)
|
if (numberRemoved < 1 || numberRemoved > 4) return;
|
||||||
return;
|
this.#score +=
|
||||||
this.#score += this.#scoreLinesModifier[numberRemoved-1] * (this.#level + 1);
|
this.#scoreLinesModifier[numberRemoved - 1] * (this.#level + 1);
|
||||||
switch (numberRemoved) {
|
switch (numberRemoved) {
|
||||||
case 1:
|
case 1:
|
||||||
this.#levelProgression += 1;
|
this.#levelProgression += 1;
|
||||||
|
@ -79,6 +80,8 @@ export default class ScoreManager {
|
||||||
case 4: // Did a tetris !
|
case 4: // Did a tetris !
|
||||||
this.#levelProgression += 8;
|
this.#levelProgression += 8;
|
||||||
break;
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,13 +92,12 @@ export default class ScoreManager {
|
||||||
*
|
*
|
||||||
* @return {boolean} True if the current level has changed
|
* @return {boolean} True if the current level has changed
|
||||||
*/
|
*/
|
||||||
canLevelUp() {
|
canLevelUp(): boolean {
|
||||||
let canLevel = this.#levelProgression > this.#level * 5;
|
const canLevel = this.#levelProgression > this.#level * 5;
|
||||||
if (canLevel) {
|
if (canLevel) {
|
||||||
this.#levelProgression -= this.#level * 5;
|
this.#levelProgression -= this.#level * 5;
|
||||||
this.#level++;
|
this.#level += 1;
|
||||||
}
|
}
|
||||||
return canLevel;
|
return canLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,27 +3,28 @@
|
||||||
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';
|
||||||
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import GameLogic from "../logic/GameLogic";
|
import i18n from 'i18n-js';
|
||||||
import type {Grid} from "../components/GridComponent";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import GridComponent from "../components/GridComponent";
|
import GameLogic from '../logic/GameLogic';
|
||||||
import Preview from "../components/Preview";
|
import type {GridType} from '../components/GridComponent';
|
||||||
import i18n from "i18n-js";
|
import GridComponent from '../components/GridComponent';
|
||||||
import MaterialHeaderButtons, {Item} from "../../../components/Overrides/CustomHeaderButton";
|
import Preview from '../components/Preview';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import MaterialHeaderButtons, {
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
Item,
|
||||||
import type {OptionsDialogButton} from "../../../components/Dialogs/OptionsDialog";
|
} from '../../../components/Overrides/CustomHeaderButton';
|
||||||
import OptionsDialog from "../../../components/Dialogs/OptionsDialog";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
|
import type {OptionsDialogButtonType} from '../../../components/Dialogs/OptionsDialog';
|
||||||
|
import OptionsDialog from '../../../components/Dialogs/OptionsDialog';
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: { params: { highScore: number }, ... },
|
route: {params: {highScore: number}},
|
||||||
theme: CustomTheme,
|
theme: CustomThemeType,
|
||||||
}
|
};
|
||||||
|
|
||||||
type State = {
|
type StateType = {
|
||||||
grid: Grid,
|
grid: GridType,
|
||||||
gameRunning: boolean,
|
|
||||||
gameTime: number,
|
gameTime: number,
|
||||||
gameScore: number,
|
gameScore: number,
|
||||||
gameLevel: number,
|
gameLevel: number,
|
||||||
|
@ -31,101 +32,320 @@ type State = {
|
||||||
dialogVisible: boolean,
|
dialogVisible: boolean,
|
||||||
dialogTitle: string,
|
dialogTitle: string,
|
||||||
dialogMessage: string,
|
dialogMessage: string,
|
||||||
dialogButtons: Array<OptionsDialogButton>,
|
dialogButtons: Array<OptionsDialogButtonType>,
|
||||||
onDialogDismiss: () => void,
|
onDialogDismiss: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
class GameMainScreen extends React.Component<PropsType, StateType> {
|
||||||
|
static getFormattedTime(seconds: number): string {
|
||||||
|
const date = new Date();
|
||||||
|
date.setHours(0);
|
||||||
|
date.setMinutes(0);
|
||||||
|
date.setSeconds(seconds);
|
||||||
|
let format;
|
||||||
|
if (date.getHours())
|
||||||
|
format = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
|
else if (date.getMinutes())
|
||||||
|
format = `${date.getMinutes()}:${date.getSeconds()}`;
|
||||||
|
else format = date.getSeconds().toString();
|
||||||
|
return format;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GameMainScreen extends React.Component<Props, State> {
|
|
||||||
|
|
||||||
logic: GameLogic;
|
logic: GameLogic;
|
||||||
|
|
||||||
highScore: number | null;
|
highScore: number | null;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.logic = new GameLogic(20, 10, this.props.theme);
|
this.logic = new GameLogic(20, 10, props.theme);
|
||||||
this.state = {
|
this.state = {
|
||||||
grid: this.logic.getCurrentGrid(),
|
grid: this.logic.getCurrentGrid(),
|
||||||
gameRunning: false,
|
|
||||||
gameTime: 0,
|
gameTime: 0,
|
||||||
gameScore: 0,
|
gameScore: 0,
|
||||||
gameLevel: 0,
|
gameLevel: 0,
|
||||||
dialogVisible: false,
|
dialogVisible: false,
|
||||||
dialogTitle: "",
|
dialogTitle: '',
|
||||||
dialogMessage: "",
|
dialogMessage: '',
|
||||||
dialogButtons: [],
|
dialogButtons: [],
|
||||||
onDialogDismiss: () => {
|
onDialogDismiss: () => {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
if (this.props.route.params != null)
|
if (props.route.params != null)
|
||||||
this.highScore = this.props.route.params.highScore;
|
this.highScore = props.route.params.highScore;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.navigation.setOptions({
|
const {navigation} = this.props;
|
||||||
|
navigation.setOptions({
|
||||||
headerRight: this.getRightButton,
|
headerRight: this.getRightButton,
|
||||||
});
|
});
|
||||||
this.startGame();
|
this.startGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.logic.stopGame();
|
this.logic.endGame(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRightButton = () => {
|
getRightButton = (): React.Node => {
|
||||||
return <MaterialHeaderButtons>
|
return (
|
||||||
|
<MaterialHeaderButtons>
|
||||||
<Item title="pause" iconName="pause" onPress={this.togglePause} />
|
<Item title="pause" iconName="pause" onPress={this.togglePause} />
|
||||||
</MaterialHeaderButtons>;
|
</MaterialHeaderButtons>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
getFormattedTime(seconds: number) {
|
onTick = (score: number, level: number, newGrid: GridType) => {
|
||||||
let date = new Date();
|
|
||||||
date.setHours(0);
|
|
||||||
date.setMinutes(0);
|
|
||||||
date.setSeconds(seconds);
|
|
||||||
let format;
|
|
||||||
if (date.getHours())
|
|
||||||
format = date.getHours() + ':' + date.getMinutes() + ':' + date.getSeconds();
|
|
||||||
else if (date.getMinutes())
|
|
||||||
format = date.getMinutes() + ':' + date.getSeconds();
|
|
||||||
else
|
|
||||||
format = date.getSeconds();
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
onTick = (score: number, level: number, newGrid: Grid) => {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
gameLevel: level,
|
gameLevel: level,
|
||||||
grid: newGrid,
|
grid: newGrid,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
onClock = (time: number) => {
|
onClock = (time: number) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
gameTime: time,
|
gameTime: time,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
updateGrid = (newGrid: Grid) => {
|
onDialogDismiss = () => {
|
||||||
this.setState({
|
this.setState({dialogVisible: false});
|
||||||
grid: newGrid,
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateGridScore = (newGrid: Grid, score: number) => {
|
onGameEnd = (time: number, score: number, isRestart: boolean) => {
|
||||||
|
const {props, state} = this;
|
||||||
this.setState({
|
this.setState({
|
||||||
grid: newGrid,
|
gameTime: time,
|
||||||
gameScore: score,
|
gameScore: score,
|
||||||
});
|
});
|
||||||
|
if (!isRestart)
|
||||||
|
props.navigation.replace('game-start', {
|
||||||
|
score: state.gameScore,
|
||||||
|
level: state.gameLevel,
|
||||||
|
time: state.gameTime,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getStatusIcons(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
}}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}}>
|
||||||
|
<Caption
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginBottom: 5,
|
||||||
|
}}>
|
||||||
|
{i18n.t('screens.game.time')}
|
||||||
|
</Caption>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="timer"
|
||||||
|
color={props.theme.colors.subtitle}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
color: props.theme.colors.subtitle,
|
||||||
|
}}>
|
||||||
|
{GameMainScreen.getFormattedTime(state.gameTime)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 20,
|
||||||
|
}}>
|
||||||
|
<Caption
|
||||||
|
style={{
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginBottom: 5,
|
||||||
|
}}>
|
||||||
|
{i18n.t('screens.game.level')}
|
||||||
|
</Caption>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="gamepad-square"
|
||||||
|
color={props.theme.colors.text}
|
||||||
|
size={20}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
}}>
|
||||||
|
{state.gameLevel}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getScoreIcon(): React.Node {
|
||||||
|
const {props, state} = this;
|
||||||
|
const highScore =
|
||||||
|
this.highScore == null || state.gameScore > this.highScore
|
||||||
|
? state.gameScore
|
||||||
|
: this.highScore;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
marginTop: 10,
|
||||||
|
marginBottom: 10,
|
||||||
|
}}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
}}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
fontSize: 20,
|
||||||
|
}}>
|
||||||
|
{i18n.t('screens.game.score', {score: state.gameScore})}
|
||||||
|
</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="star"
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
size={20}
|
||||||
|
style={{
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
marginLeft: 5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 5,
|
||||||
|
}}>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
marginLeft: 5,
|
||||||
|
fontSize: 10,
|
||||||
|
color: props.theme.colors.textDisabled,
|
||||||
|
}}>
|
||||||
|
{i18n.t('screens.game.highScore', {score: highScore})}
|
||||||
|
</Text>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="star"
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
size={10}
|
||||||
|
style={{
|
||||||
|
marginTop: 'auto',
|
||||||
|
marginBottom: 'auto',
|
||||||
|
marginLeft: 5,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getControlButtons(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
height: 80,
|
||||||
|
flexDirection: 'row',
|
||||||
|
}}>
|
||||||
|
<IconButton
|
||||||
|
icon="rotate-right-variant"
|
||||||
|
size={40}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.rotatePressed(this.updateGrid);
|
||||||
|
}}
|
||||||
|
style={{flex: 1}}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row',
|
||||||
|
flex: 4,
|
||||||
|
}}>
|
||||||
|
<IconButton
|
||||||
|
icon="chevron-left"
|
||||||
|
size={40}
|
||||||
|
style={{flex: 1}}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.leftPressedIn(this.updateGrid);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon="chevron-right"
|
||||||
|
size={40}
|
||||||
|
style={{flex: 1}}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.rightPressed(this.updateGrid);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<IconButton
|
||||||
|
icon="arrow-down-bold"
|
||||||
|
size={40}
|
||||||
|
onPressIn={() => {
|
||||||
|
this.logic.downPressedIn(this.updateGridScore);
|
||||||
|
}}
|
||||||
|
onPress={() => {
|
||||||
|
this.logic.pressedOut();
|
||||||
|
}}
|
||||||
|
style={{flex: 1}}
|
||||||
|
color={props.theme.colors.tetrisScore}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGrid = (newGrid: GridType) => {
|
||||||
|
this.setState({
|
||||||
|
grid: newGrid,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updateGridScore = (newGrid: GridType, score?: number) => {
|
||||||
|
this.setState((prevState: StateType): {
|
||||||
|
grid: GridType,
|
||||||
|
gameScore: number,
|
||||||
|
} => ({
|
||||||
|
grid: newGrid,
|
||||||
|
gameScore: score != null ? score : prevState.gameScore,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
togglePause = () => {
|
togglePause = () => {
|
||||||
this.logic.togglePause();
|
this.logic.togglePause();
|
||||||
if (this.logic.isGamePaused())
|
if (this.logic.isGamePaused()) this.showPausePopup();
|
||||||
this.showPausePopup();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
onDialogDismiss = () => this.setState({dialogVisible: false});
|
|
||||||
|
|
||||||
showPausePopup = () => {
|
showPausePopup = () => {
|
||||||
const onDismiss = () => {
|
const onDismiss = () => {
|
||||||
|
@ -134,228 +354,56 @@ class GameMainScreen extends React.Component<Props, State> {
|
||||||
};
|
};
|
||||||
this.setState({
|
this.setState({
|
||||||
dialogVisible: true,
|
dialogVisible: true,
|
||||||
dialogTitle: i18n.t("screens.game.pause"),
|
dialogTitle: i18n.t('screens.game.pause'),
|
||||||
dialogMessage: i18n.t("screens.game.pauseMessage"),
|
dialogMessage: i18n.t('screens.game.pauseMessage'),
|
||||||
dialogButtons: [
|
dialogButtons: [
|
||||||
{
|
{
|
||||||
title: i18n.t("screens.game.restart.text"),
|
title: i18n.t('screens.game.restart.text'),
|
||||||
onPress: this.showRestartConfirm
|
onPress: this.showRestartConfirm,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t("screens.game.resume"),
|
title: i18n.t('screens.game.resume'),
|
||||||
onPress: onDismiss
|
onPress: onDismiss,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
onDialogDismiss: onDismiss,
|
onDialogDismiss: onDismiss,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
showRestartConfirm = () => {
|
showRestartConfirm = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
dialogVisible: true,
|
dialogVisible: true,
|
||||||
dialogTitle: i18n.t("screens.game.restart.confirm"),
|
dialogTitle: i18n.t('screens.game.restart.confirm'),
|
||||||
dialogMessage: i18n.t("screens.game.restart.confirmMessage"),
|
dialogMessage: i18n.t('screens.game.restart.confirmMessage'),
|
||||||
dialogButtons: [
|
dialogButtons: [
|
||||||
{
|
{
|
||||||
title: i18n.t("screens.game.restart.confirmYes"),
|
title: i18n.t('screens.game.restart.confirmYes'),
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
this.onDialogDismiss();
|
this.onDialogDismiss();
|
||||||
this.startGame();
|
this.startGame();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t("screens.game.restart.confirmNo"),
|
title: i18n.t('screens.game.restart.confirmNo'),
|
||||||
onPress: this.showPausePopup
|
onPress: this.showPausePopup,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
onDialogDismiss: this.showPausePopup,
|
onDialogDismiss: this.showPausePopup,
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
startGame = () => {
|
startGame = () => {
|
||||||
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
this.logic.startGame(this.onTick, this.onClock, this.onGameEnd);
|
||||||
this.setState({
|
};
|
||||||
gameRunning: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onGameEnd = (time: number, score: number, isRestart: boolean) => {
|
render(): React.Node {
|
||||||
this.setState({
|
const {props, state} = this;
|
||||||
gameTime: time,
|
|
||||||
gameScore: score,
|
|
||||||
gameRunning: false,
|
|
||||||
});
|
|
||||||
if (!isRestart)
|
|
||||||
this.props.navigation.replace(
|
|
||||||
"game-start",
|
|
||||||
{
|
|
||||||
score: this.state.gameScore,
|
|
||||||
level: this.state.gameLevel,
|
|
||||||
time: this.state.gameTime,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getStatusIcons() {
|
|
||||||
return (
|
|
||||||
<View style={{
|
|
||||||
flex: 1,
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto"
|
|
||||||
}}>
|
|
||||||
<View style={{
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
}}>
|
|
||||||
<Caption style={{
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
marginBottom: 5,
|
|
||||||
}}>{i18n.t("screens.game.time")}</Caption>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: "row"
|
|
||||||
}}>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'timer'}
|
|
||||||
color={this.props.theme.colors.subtitle}
|
|
||||||
size={20}/>
|
|
||||||
<Text style={{
|
|
||||||
marginLeft: 5,
|
|
||||||
color: this.props.theme.colors.subtitle
|
|
||||||
}}>{this.getFormattedTime(this.state.gameTime)}</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
</View>
|
|
||||||
<View style={{
|
|
||||||
marginLeft: 'auto',
|
|
||||||
marginRight: 'auto',
|
|
||||||
marginTop: 20,
|
|
||||||
}}>
|
|
||||||
<Caption style={{
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
marginBottom: 5,
|
|
||||||
}}>{i18n.t("screens.game.level")}</Caption>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: "row"
|
|
||||||
}}>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'gamepad-square'}
|
|
||||||
color={this.props.theme.colors.text}
|
|
||||||
size={20}/>
|
|
||||||
<Text style={{
|
|
||||||
marginLeft: 5
|
|
||||||
}}>{this.state.gameLevel}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getScoreIcon() {
|
|
||||||
let highScore = this.highScore == null || this.state.gameScore > this.highScore
|
|
||||||
? this.state.gameScore
|
|
||||||
: this.highScore;
|
|
||||||
return (
|
|
||||||
<View style={{
|
|
||||||
marginTop: 10,
|
|
||||||
marginBottom: 10,
|
|
||||||
}}>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
marginLeft: 5,
|
|
||||||
fontSize: 20,
|
|
||||||
}}>{i18n.t("screens.game.score", {score: this.state.gameScore})}</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'star'}
|
|
||||||
color={this.props.theme.colors.tetrisScore}
|
|
||||||
size={20}
|
|
||||||
style={{
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
marginLeft: 5
|
|
||||||
}}/>
|
|
||||||
</View>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: "row",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
marginTop: 5,
|
|
||||||
}}>
|
|
||||||
<Text style={{
|
|
||||||
marginLeft: 5,
|
|
||||||
fontSize: 10,
|
|
||||||
color: this.props.theme.colors.textDisabled
|
|
||||||
}}>{i18n.t("screens.game.highScore", {score: highScore})}</Text>
|
|
||||||
<MaterialCommunityIcons
|
|
||||||
name={'star'}
|
|
||||||
color={this.props.theme.colors.tetrisScore}
|
|
||||||
size={10}
|
|
||||||
style={{
|
|
||||||
marginTop: "auto",
|
|
||||||
marginBottom: "auto",
|
|
||||||
marginLeft: 5
|
|
||||||
}}/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getControlButtons() {
|
|
||||||
return (
|
|
||||||
<View style={{
|
|
||||||
height: 80,
|
|
||||||
flexDirection: "row"
|
|
||||||
}}>
|
|
||||||
<IconButton
|
|
||||||
icon="rotate-right-variant"
|
|
||||||
size={40}
|
|
||||||
onPress={() => this.logic.rotatePressed(this.updateGrid)}
|
|
||||||
style={{flex: 1}}
|
|
||||||
/>
|
|
||||||
<View style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
flex: 4
|
|
||||||
}}>
|
|
||||||
<IconButton
|
|
||||||
icon="chevron-left"
|
|
||||||
size={40}
|
|
||||||
style={{flex: 1}}
|
|
||||||
onPress={() => this.logic.pressedOut()}
|
|
||||||
onPressIn={() => this.logic.leftPressedIn(this.updateGrid)}
|
|
||||||
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon="chevron-right"
|
|
||||||
size={40}
|
|
||||||
style={{flex: 1}}
|
|
||||||
onPress={() => this.logic.pressedOut()}
|
|
||||||
onPressIn={() => this.logic.rightPressed(this.updateGrid)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<IconButton
|
|
||||||
icon="arrow-down-bold"
|
|
||||||
size={40}
|
|
||||||
onPressIn={() => this.logic.downPressedIn(this.updateGridScore)}
|
|
||||||
onPress={() => this.logic.pressedOut()}
|
|
||||||
style={{flex: 1}}
|
|
||||||
color={this.props.theme.colors.tetrisScore}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
<View style={{
|
<View
|
||||||
|
style={{
|
||||||
flex: 1,
|
flex: 1,
|
||||||
flexDirection: "row",
|
flexDirection: 'row',
|
||||||
}}>
|
}}>
|
||||||
{this.getStatusIcons()}
|
{this.getStatusIcons()}
|
||||||
<View style={{flex: 4}}>
|
<View style={{flex: 4}}>
|
||||||
|
@ -363,12 +411,12 @@ class GameMainScreen extends React.Component<Props, State> {
|
||||||
<GridComponent
|
<GridComponent
|
||||||
width={this.logic.getWidth()}
|
width={this.logic.getWidth()}
|
||||||
height={this.logic.getHeight()}
|
height={this.logic.getHeight()}
|
||||||
grid={this.state.grid}
|
grid={state.grid}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: this.props.theme.colors.tetrisBackground,
|
backgroundColor: props.theme.colors.tetrisBackground,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
marginLeft: "auto",
|
marginLeft: 'auto',
|
||||||
marginRight: "auto",
|
marginRight: 'auto',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -387,16 +435,15 @@ class GameMainScreen extends React.Component<Props, State> {
|
||||||
{this.getControlButtons()}
|
{this.getControlButtons()}
|
||||||
|
|
||||||
<OptionsDialog
|
<OptionsDialog
|
||||||
visible={this.state.dialogVisible}
|
visible={state.dialogVisible}
|
||||||
title={this.state.dialogTitle}
|
title={state.dialogTitle}
|
||||||
message={this.state.dialogMessage}
|
message={state.dialogMessage}
|
||||||
buttons={this.state.dialogButtons}
|
buttons={state.dialogButtons}
|
||||||
onDismiss={this.state.onDialogDismiss}
|
onDismiss={state.onDialogDismiss}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(GameMainScreen);
|
export default withTheme(GameMainScreen);
|
||||||
|
|
|
@ -1,111 +1,105 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from 'react';
|
||||||
import {StackNavigationProp} from "@react-navigation/stack";
|
import {StackNavigationProp} from '@react-navigation/stack';
|
||||||
import type {CustomTheme} from "../../../managers/ThemeManager";
|
import {
|
||||||
import {Button, Card, Divider, Headline, Paragraph, Text, withTheme} from "react-native-paper";
|
Button,
|
||||||
import {View} from "react-native";
|
Card,
|
||||||
import i18n from "i18n-js";
|
Divider,
|
||||||
import Mascot, {MASCOT_STYLE} from "../../../components/Mascot/Mascot";
|
Headline,
|
||||||
import MascotPopup from "../../../components/Mascot/MascotPopup";
|
Paragraph,
|
||||||
import AsyncStorageManager from "../../../managers/AsyncStorageManager";
|
Text,
|
||||||
import type {Grid} from "../components/GridComponent";
|
withTheme,
|
||||||
import GridComponent from "../components/GridComponent";
|
} from 'react-native-paper';
|
||||||
import GridManager from "../logic/GridManager";
|
import {View} from 'react-native';
|
||||||
import Piece from "../logic/Piece";
|
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 SpeechArrow from "../../../components/Mascot/SpeechArrow";
|
import type {CustomThemeType} from '../../../managers/ThemeManager';
|
||||||
import CollapsibleScrollView from "../../../components/Collapsible/CollapsibleScrollView";
|
import Mascot, {MASCOT_STYLE} from '../../../components/Mascot/Mascot';
|
||||||
|
import MascotPopup from '../../../components/Mascot/MascotPopup';
|
||||||
|
import AsyncStorageManager from '../../../managers/AsyncStorageManager';
|
||||||
|
import type {GridType} from '../components/GridComponent';
|
||||||
|
import GridComponent from '../components/GridComponent';
|
||||||
|
import GridManager from '../logic/GridManager';
|
||||||
|
import Piece from '../logic/Piece';
|
||||||
|
import SpeechArrow from '../../../components/Mascot/SpeechArrow';
|
||||||
|
import CollapsibleScrollView from '../../../components/Collapsible/CollapsibleScrollView';
|
||||||
|
|
||||||
type GameStats = {
|
type GameStatsType = {
|
||||||
score: number,
|
score: number,
|
||||||
level: number,
|
level: number,
|
||||||
time: number,
|
time: number,
|
||||||
}
|
};
|
||||||
|
|
||||||
type Props = {
|
type PropsType = {
|
||||||
navigation: StackNavigationProp,
|
navigation: StackNavigationProp,
|
||||||
route: {
|
route: {
|
||||||
params: GameStats
|
params: GameStatsType,
|
||||||
},
|
},
|
||||||
theme: CustomTheme,
|
theme: CustomThemeType,
|
||||||
}
|
};
|
||||||
|
|
||||||
class GameStartScreen extends React.Component<Props> {
|
|
||||||
|
|
||||||
|
class GameStartScreen extends React.Component<PropsType> {
|
||||||
gridManager: GridManager;
|
gridManager: GridManager;
|
||||||
|
|
||||||
scores: Array<number>;
|
scores: Array<number>;
|
||||||
|
|
||||||
gameStats: GameStats | null;
|
gameStats: GameStatsType | null;
|
||||||
|
|
||||||
isHighScore: boolean;
|
isHighScore: boolean;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: PropsType) {
|
||||||
super(props);
|
super(props);
|
||||||
this.gridManager = new GridManager(4, 4, props.theme);
|
this.gridManager = new GridManager(4, 4, props.theme);
|
||||||
this.scores = AsyncStorageManager.getObject(AsyncStorageManager.PREFERENCES.gameScores.key);
|
this.scores = AsyncStorageManager.getObject(
|
||||||
this.scores.sort((a, b) => b - a);
|
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||||
if (this.props.route.params != null)
|
);
|
||||||
this.recoverGameScore();
|
this.scores.sort((a: number, b: number): number => b - a);
|
||||||
|
if (props.route.params != null) this.recoverGameScore();
|
||||||
}
|
}
|
||||||
|
|
||||||
recoverGameScore() {
|
getPiecesBackground(): React.Node {
|
||||||
this.gameStats = this.props.route.params;
|
const {theme} = this.props;
|
||||||
this.isHighScore = this.scores.length === 0 || this.gameStats.score > this.scores[0];
|
const gridList = [];
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 18; i += 1) {
|
||||||
if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
|
|
||||||
this.scores.splice(i, 0, this.gameStats.score);
|
|
||||||
break;
|
|
||||||
} else if (this.scores.length <= i) {
|
|
||||||
this.scores.push(this.gameStats.score);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.scores.length > 3)
|
|
||||||
this.scores.splice(3, 1);
|
|
||||||
AsyncStorageManager.set(AsyncStorageManager.PREFERENCES.gameScores.key, this.scores);
|
|
||||||
}
|
|
||||||
|
|
||||||
getPiecesBackground() {
|
|
||||||
let gridList = [];
|
|
||||||
for (let i = 0; i < 18; i++) {
|
|
||||||
gridList.push(this.gridManager.getEmptyGrid(4, 4));
|
gridList.push(this.gridManager.getEmptyGrid(4, 4));
|
||||||
const piece = new Piece(this.props.theme);
|
const piece = new Piece(theme);
|
||||||
piece.toGrid(gridList[i], true);
|
piece.toGrid(gridList[i], true);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
position: "absolute",
|
style={{
|
||||||
width: "100%",
|
position: 'absolute',
|
||||||
height: "100%",
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
}}>
|
}}>
|
||||||
{gridList.map((item: Grid, index: number) => {
|
{gridList.map((item: GridType, index: number): React.Node => {
|
||||||
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);
|
||||||
const left = (index % 6) * 20;
|
const left = (index % 6) * 20;
|
||||||
const animDelay = size * 20;
|
const animDelay = size * 20;
|
||||||
const animDuration = 2 * (2000 - (size * 30));
|
const animDuration = 2 * (2000 - size * 30);
|
||||||
return (
|
return (
|
||||||
<Animatable.View
|
<Animatable.View
|
||||||
animation={"fadeInDownBig"}
|
animation="fadeInDownBig"
|
||||||
delay={animDelay}
|
delay={animDelay}
|
||||||
duration={animDuration}
|
duration={animDuration}
|
||||||
key={"piece" + index.toString()}
|
key={`piece${index.toString()}`}
|
||||||
style={{
|
style={{
|
||||||
width: size + "%",
|
width: `${size}%`,
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: top + "%",
|
top: `${top}%`,
|
||||||
left: left + "%",
|
left: `${left}%`,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<GridComponent
|
<GridComponent
|
||||||
width={4}
|
width={4}
|
||||||
height={4}
|
height={4}
|
||||||
grid={item}
|
grid={item}
|
||||||
style={{
|
style={{
|
||||||
transform: [{rotateZ: rot + "deg"}],
|
transform: [{rotateZ: `${rot}deg`}],
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
|
@ -115,26 +109,30 @@ class GameStartScreen extends React.Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPostGameContent(stats: GameStats) {
|
getPostGameContent(stats: GameStatsType): React.Node {
|
||||||
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
flex: 1
|
style={{
|
||||||
|
flex: 1,
|
||||||
}}>
|
}}>
|
||||||
<Mascot
|
<Mascot
|
||||||
emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
|
emotion={this.isHighScore ? MASCOT_STYLE.LOVE : MASCOT_STYLE.NORMAL}
|
||||||
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' : null,
|
||||||
marginRight: this.isHighScore ? "auto" : null,
|
marginRight: this.isHighScore ? 'auto' : null,
|
||||||
}}/>
|
}}
|
||||||
<SpeechArrow
|
|
||||||
style={{marginLeft: this.isHighScore ? "60%" : "20%"}}
|
|
||||||
size={20}
|
|
||||||
color={this.props.theme.colors.mascotMessageArrow}
|
|
||||||
/>
|
/>
|
||||||
<Card style={{
|
<SpeechArrow
|
||||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
style={{marginLeft: this.isHighScore ? '60%' : '20%'}}
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.mascotMessageArrow}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
borderColor: props.theme.colors.mascotMessageArrow,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
|
@ -142,95 +140,101 @@ class GameStartScreen extends React.Component<Props> {
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Headline
|
<Headline
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: 'center',
|
||||||
color: this.isHighScore
|
color: this.isHighScore
|
||||||
? this.props.theme.colors.gameGold
|
? props.theme.colors.gameGold
|
||||||
: this.props.theme.colors.primary
|
: props.theme.colors.primary,
|
||||||
}}>
|
}}>
|
||||||
{this.isHighScore
|
{this.isHighScore
|
||||||
? i18n.t("screens.game.newHighScore")
|
? i18n.t('screens.game.newHighScore')
|
||||||
: i18n.t("screens.game.gameOver")}
|
: i18n.t('screens.game.gameOver')}
|
||||||
</Headline>
|
</Headline>
|
||||||
<Divider />
|
<Divider />
|
||||||
<View style={{
|
<View
|
||||||
flexDirection: "row",
|
style={{
|
||||||
marginLeft: "auto",
|
flexDirection: 'row',
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
}}>
|
}}>
|
||||||
<Text style={{
|
<Text
|
||||||
|
style={{
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
}}>
|
}}>
|
||||||
{i18n.t("screens.game.score", {score: stats.score})}
|
{i18n.t('screens.game.score', {score: stats.score})}
|
||||||
</Text>
|
</Text>
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={'star'}
|
name="star"
|
||||||
color={this.props.theme.colors.tetrisScore}
|
color={props.theme.colors.tetrisScore}
|
||||||
size={30}
|
size={30}
|
||||||
style={{
|
style={{
|
||||||
marginLeft: 5
|
marginLeft: 5,
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={{
|
<View
|
||||||
flexDirection: "row",
|
style={{
|
||||||
marginLeft: "auto",
|
flexDirection: 'row',
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
}}>
|
}}>
|
||||||
<Text>{i18n.t("screens.game.level")}</Text>
|
<Text>{i18n.t('screens.game.level')}</Text>
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
}}
|
}}
|
||||||
name={"gamepad-square"}
|
name="gamepad-square"
|
||||||
size={20}
|
size={20}
|
||||||
color={this.props.theme.colors.textDisabled}
|
color={props.theme.colors.textDisabled}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>{stats.level}</Text>
|
||||||
{stats.level}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={{
|
<View
|
||||||
flexDirection: "row",
|
style={{
|
||||||
marginLeft: "auto",
|
flexDirection: 'row',
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
}}>
|
}}>
|
||||||
<Text>{i18n.t("screens.game.time")}</Text>
|
<Text>{i18n.t('screens.game.time')}</Text>
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
style={{
|
style={{
|
||||||
marginRight: 5,
|
marginRight: 5,
|
||||||
marginLeft: 5,
|
marginLeft: 5,
|
||||||
}}
|
}}
|
||||||
name={"timer"}
|
name="timer"
|
||||||
size={20}
|
size={20}
|
||||||
color={this.props.theme.colors.textDisabled}
|
color={props.theme.colors.textDisabled}
|
||||||
/>
|
/>
|
||||||
<Text>
|
<Text>{stats.time}</Text>
|
||||||
{stats.time}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
</View>
|
</View>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWelcomeText() {
|
getWelcomeText(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<Mascot emotion={MASCOT_STYLE.COOL} style={{
|
<Mascot
|
||||||
width: "40%",
|
emotion={MASCOT_STYLE.COOL}
|
||||||
marginLeft: "auto",
|
style={{
|
||||||
marginRight: "auto",
|
width: '40%',
|
||||||
}}/>
|
marginLeft: 'auto',
|
||||||
<SpeechArrow
|
marginRight: 'auto',
|
||||||
style={{marginLeft: "60%"}}
|
}}
|
||||||
size={20}
|
|
||||||
color={this.props.theme.colors.mascotMessageArrow}
|
|
||||||
/>
|
/>
|
||||||
<Card style={{
|
<SpeechArrow
|
||||||
borderColor: this.props.theme.colors.mascotMessageArrow,
|
style={{marginLeft: '60%'}}
|
||||||
|
size={20}
|
||||||
|
color={props.theme.colors.mascotMessageArrow}
|
||||||
|
/>
|
||||||
|
<Card
|
||||||
|
style={{
|
||||||
|
borderColor: props.theme.colors.mascotMessageArrow,
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
marginLeft: 10,
|
marginLeft: 10,
|
||||||
marginRight: 10,
|
marginRight: 10,
|
||||||
|
@ -238,18 +242,18 @@ class GameStartScreen extends React.Component<Props> {
|
||||||
<Card.Content>
|
<Card.Content>
|
||||||
<Headline
|
<Headline
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: 'center',
|
||||||
color: this.props.theme.colors.primary
|
color: props.theme.colors.primary,
|
||||||
}}>
|
}}>
|
||||||
{i18n.t("screens.game.welcomeTitle")}
|
{i18n.t('screens.game.welcomeTitle')}
|
||||||
</Headline>
|
</Headline>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Paragraph
|
<Paragraph
|
||||||
style={{
|
style={{
|
||||||
textAlign: "center",
|
textAlign: 'center',
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
}}>
|
}}>
|
||||||
{i18n.t("screens.game.welcomeMessage")}
|
{i18n.t('screens.game.welcomeMessage')}
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -257,93 +261,88 @@ class GameStartScreen extends React.Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPodiumRender(place: 1 | 2 | 3, score: string) {
|
getPodiumRender(place: 1 | 2 | 3, score: string): React.Node {
|
||||||
let icon = "podium-gold";
|
const {props} = this;
|
||||||
let color = this.props.theme.colors.gameGold;
|
let icon = 'podium-gold';
|
||||||
|
let color = props.theme.colors.gameGold;
|
||||||
let fontSize = 20;
|
let fontSize = 20;
|
||||||
let size = 70;
|
let size = 70;
|
||||||
if (place === 2) {
|
if (place === 2) {
|
||||||
icon = "podium-silver";
|
icon = 'podium-silver';
|
||||||
color = this.props.theme.colors.gameSilver;
|
color = props.theme.colors.gameSilver;
|
||||||
fontSize = 18;
|
fontSize = 18;
|
||||||
size = 60;
|
size = 60;
|
||||||
} else if (place === 3) {
|
} else if (place === 3) {
|
||||||
icon = "podium-bronze";
|
icon = 'podium-bronze';
|
||||||
color = this.props.theme.colors.gameBronze;
|
color = props.theme.colors.gameBronze;
|
||||||
fontSize = 15;
|
fontSize = 15;
|
||||||
size = 50;
|
size = 50;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
marginLeft: place === 2 ? 20 : "auto",
|
style={{
|
||||||
marginRight: place === 3 ? 20 : "auto",
|
marginLeft: place === 2 ? 20 : 'auto',
|
||||||
flexDirection: "column",
|
marginRight: place === 3 ? 20 : 'auto',
|
||||||
alignItems: "center",
|
flexDirection: 'column',
|
||||||
justifyContent: "flex-end",
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
}}>
|
}}>
|
||||||
{
|
{this.isHighScore && place === 1 ? (
|
||||||
this.isHighScore && place === 1
|
|
||||||
?
|
|
||||||
<Animatable.View
|
<Animatable.View
|
||||||
animation={"swing"}
|
animation="swing"
|
||||||
iterationCount={"infinite"}
|
iterationCount="infinite"
|
||||||
duration={2000}
|
duration={2000}
|
||||||
delay={1000}
|
delay={1000}
|
||||||
useNativeDriver={true}
|
useNativeDriver
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: 'absolute',
|
||||||
top: -20
|
top: -20,
|
||||||
}}
|
}}>
|
||||||
>
|
|
||||||
<Animatable.View
|
<Animatable.View
|
||||||
animation={"pulse"}
|
animation="pulse"
|
||||||
iterationCount={"infinite"}
|
iterationCount="infinite"
|
||||||
useNativeDriver={true}
|
useNativeDriver>
|
||||||
>
|
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={"decagram"}
|
name="decagram"
|
||||||
color={this.props.theme.colors.gameGold}
|
color={props.theme.colors.gameGold}
|
||||||
size={150}
|
size={150}
|
||||||
/>
|
/>
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
</Animatable.View>
|
</Animatable.View>
|
||||||
|
) : null}
|
||||||
: null
|
|
||||||
}
|
|
||||||
<MaterialCommunityIcons
|
<MaterialCommunityIcons
|
||||||
name={icon}
|
name={icon}
|
||||||
color={this.isHighScore && place === 1 ? "#fff" : color}
|
color={this.isHighScore && place === 1 ? '#fff' : color}
|
||||||
size={size}
|
size={size}
|
||||||
/>
|
/>
|
||||||
<Text style={{
|
<Text
|
||||||
textAlign: "center",
|
style={{
|
||||||
fontWeight: place === 1 ? "bold" : null,
|
textAlign: 'center',
|
||||||
fontSize: fontSize,
|
fontWeight: place === 1 ? 'bold' : null,
|
||||||
}}>{score}</Text>
|
fontSize,
|
||||||
|
}}>
|
||||||
|
{score}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTopScoresRender() {
|
getTopScoresRender(): React.Node {
|
||||||
const gold = this.scores.length > 0
|
const gold = this.scores.length > 0 ? this.scores[0] : '-';
|
||||||
? this.scores[0]
|
const silver = this.scores.length > 1 ? this.scores[1] : '-';
|
||||||
: "-";
|
const bronze = this.scores.length > 2 ? this.scores[2] : '-';
|
||||||
const silver = this.scores.length > 1
|
|
||||||
? this.scores[1]
|
|
||||||
: "-";
|
|
||||||
const bronze = this.scores.length > 2
|
|
||||||
? this.scores[2]
|
|
||||||
: "-";
|
|
||||||
return (
|
return (
|
||||||
<View style={{
|
<View
|
||||||
|
style={{
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
marginTop: 20
|
marginTop: 20,
|
||||||
}}>
|
}}>
|
||||||
{this.getPodiumRender(1, gold.toString())}
|
{this.getPodiumRender(1, gold.toString())}
|
||||||
<View style={{
|
<View
|
||||||
flexDirection: "row",
|
style={{
|
||||||
marginLeft: "auto",
|
flexDirection: 'row',
|
||||||
marginRight: "auto",
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
}}>
|
}}>
|
||||||
{this.getPodiumRender(3, bronze.toString())}
|
{this.getPodiumRender(3, bronze.toString())}
|
||||||
{this.getPodiumRender(2, silver.toString())}
|
{this.getPodiumRender(2, silver.toString())}
|
||||||
|
@ -352,76 +351,90 @@ class GameStartScreen extends React.Component<Props> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMainContent() {
|
getMainContent(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
{
|
{this.gameStats != null
|
||||||
this.gameStats != null
|
|
||||||
? this.getPostGameContent(this.gameStats)
|
? this.getPostGameContent(this.gameStats)
|
||||||
: this.getWelcomeText()
|
: this.getWelcomeText()}
|
||||||
}
|
|
||||||
<Button
|
<Button
|
||||||
icon={"play"}
|
icon="play"
|
||||||
mode={"contained"}
|
mode="contained"
|
||||||
onPress={() => this.props.navigation.replace(
|
onPress={() => {
|
||||||
"game-main",
|
props.navigation.replace('game-main', {
|
||||||
{
|
highScore: this.scores.length > 0 ? this.scores[0] : null,
|
||||||
highScore: this.scores.length > 0
|
});
|
||||||
? this.scores[0]
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
style={{
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
marginTop: 10,
|
|
||||||
}}
|
}}
|
||||||
>
|
style={{
|
||||||
{i18n.t("screens.game.play")}
|
marginLeft: 'auto',
|
||||||
|
marginRight: 'auto',
|
||||||
|
marginTop: 10,
|
||||||
|
}}>
|
||||||
|
{i18n.t('screens.game.play')}
|
||||||
</Button>
|
</Button>
|
||||||
{this.getTopScoresRender()}
|
{this.getTopScoresRender()}
|
||||||
</View>
|
</View>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
keyExtractor = (item: number) => item.toString();
|
keyExtractor = (item: number): string => item.toString();
|
||||||
|
|
||||||
render() {
|
recoverGameScore() {
|
||||||
|
const {route} = this.props;
|
||||||
|
this.gameStats = route.params;
|
||||||
|
this.isHighScore =
|
||||||
|
this.scores.length === 0 || this.gameStats.score > this.scores[0];
|
||||||
|
for (let i = 0; i < 3; i += 1) {
|
||||||
|
if (this.scores.length > i && this.gameStats.score > this.scores[i]) {
|
||||||
|
this.scores.splice(i, 0, this.gameStats.score);
|
||||||
|
break;
|
||||||
|
} else if (this.scores.length <= i) {
|
||||||
|
this.scores.push(this.gameStats.score);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.scores.length > 3) this.scores.splice(3, 1);
|
||||||
|
AsyncStorageManager.set(
|
||||||
|
AsyncStorageManager.PREFERENCES.gameScores.key,
|
||||||
|
this.scores,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.Node {
|
||||||
|
const {props} = this;
|
||||||
return (
|
return (
|
||||||
<View style={{flex: 1}}>
|
<View style={{flex: 1}}>
|
||||||
{this.getPiecesBackground()}
|
{this.getPiecesBackground()}
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
style={{flex: 1}}
|
style={{flex: 1}}
|
||||||
colors={[
|
colors={[
|
||||||
this.props.theme.colors.background + "00",
|
`${props.theme.colors.background}00`,
|
||||||
this.props.theme.colors.background
|
props.theme.colors.background,
|
||||||
]}
|
]}
|
||||||
start={{x: 0, y: 0}}
|
start={{x: 0, y: 0}}
|
||||||
end={{x: 0, y: 1}}
|
end={{x: 0, y: 1}}>
|
||||||
>
|
|
||||||
<CollapsibleScrollView>
|
<CollapsibleScrollView>
|
||||||
{this.getMainContent()}
|
{this.getMainContent()}
|
||||||
<MascotPopup
|
<MascotPopup
|
||||||
prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
|
prefKey={AsyncStorageManager.PREFERENCES.gameStartShowBanner.key}
|
||||||
title={i18n.t("screens.game.mascotDialog.title")}
|
title={i18n.t('screens.game.mascotDialog.title')}
|
||||||
message={i18n.t("screens.game.mascotDialog.message")}
|
message={i18n.t('screens.game.mascotDialog.message')}
|
||||||
icon={"gamepad-variant"}
|
icon="gamepad-variant"
|
||||||
buttons={{
|
buttons={{
|
||||||
action: null,
|
action: null,
|
||||||
cancel: {
|
cancel: {
|
||||||
message: i18n.t("screens.game.mascotDialog.button"),
|
message: i18n.t('screens.game.mascotDialog.button'),
|
||||||
icon: "check",
|
icon: 'check',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
emotion={MASCOT_STYLE.COOL}
|
emotion={MASCOT_STYLE.COOL}
|
||||||
/>
|
/>
|
||||||
</CollapsibleScrollView>
|
</CollapsibleScrollView>
|
||||||
</LinearGradient>
|
</LinearGradient>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTheme(GameStartScreen);
|
export default withTheme(GameStartScreen);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue