Improved game organization
This commit is contained in:
parent
965ddd3cb2
commit
c75a7dc8fc
13 changed files with 579 additions and 309 deletions
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import Tetromino from "./Tetromino";
|
import Piece from "./Piece";
|
||||||
|
|
||||||
export default class GameLogic {
|
export default class GameLogic {
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ export default class GameLogic {
|
||||||
score: number;
|
score: number;
|
||||||
level: number;
|
level: number;
|
||||||
|
|
||||||
currentObject: Tetromino;
|
currentObject: Piece;
|
||||||
|
|
||||||
gameTick: number;
|
gameTick: number;
|
||||||
gameTickInterval: IntervalID;
|
gameTickInterval: IntervalID;
|
||||||
|
@ -39,7 +39,7 @@ export default class GameLogic {
|
||||||
autoRepeatActivationDelay: number;
|
autoRepeatActivationDelay: number;
|
||||||
autoRepeatDelay: number;
|
autoRepeatDelay: number;
|
||||||
|
|
||||||
nextPieces: Array<Tetromino>;
|
nextPieces: Array<Piece>;
|
||||||
nextPiecesCount: number;
|
nextPiecesCount: number;
|
||||||
|
|
||||||
onTick: Function;
|
onTick: Function;
|
||||||
|
@ -62,12 +62,11 @@ export default class GameLogic {
|
||||||
this.nextPiecesCount = 3;
|
this.nextPiecesCount = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextPieces() {
|
getNextPiecesPreviews() {
|
||||||
let finalArray = [];
|
let finalArray = [];
|
||||||
for (let i = 0; i < this.nextPieces.length; i++) {
|
for (let i = 0; i < this.nextPieces.length; i++) {
|
||||||
finalArray.push(this.getEmptyGrid(4, 4));
|
finalArray.push(this.getEmptyGrid(4, 4));
|
||||||
let coord = this.nextPieces[i].getCellsCoordinates(false);
|
this.nextPieces[i].toGrid(finalArray[i], true);
|
||||||
this.tetrominoToGrid(this.nextPieces[i], coord, finalArray[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return finalArray;
|
return finalArray;
|
||||||
|
@ -113,14 +112,8 @@ export default class GameLogic {
|
||||||
}
|
}
|
||||||
|
|
||||||
getFinalGrid() {
|
getFinalGrid() {
|
||||||
let coord = this.currentObject.getCellsCoordinates(true);
|
|
||||||
let finalGrid = this.getGridCopy();
|
let finalGrid = this.getGridCopy();
|
||||||
for (let i = 0; i < coord.length; i++) {
|
this.currentObject.toGrid(finalGrid, false);
|
||||||
finalGrid[coord[i].y][coord[i].x] = {
|
|
||||||
color: this.currentObject.getColor(),
|
|
||||||
isEmpty: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return finalGrid;
|
return finalGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,19 +130,9 @@ export default class GameLogic {
|
||||||
return canLevel;
|
return canLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
tetrominoToGrid(object: Object, coord : Array<Object>, grid: Array<Array<Object>>) {
|
|
||||||
for (let i = 0; i < coord.length; i++) {
|
|
||||||
grid[coord[i].y][coord[i].x] = {
|
|
||||||
color: object.getColor(),
|
|
||||||
isEmpty: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
freezeTetromino() {
|
freezeTetromino() {
|
||||||
let coord = this.currentObject.getCellsCoordinates(true);
|
this.currentObject.toGrid(this.currentGrid, false);
|
||||||
this.tetrominoToGrid(this.currentObject, coord, this.currentGrid);
|
this.clearLines(this.getLinesToClear(this.currentObject.getCoordinates()));
|
||||||
this.clearLines(this.getLinesToClear(coord));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearLines(lines: Array<number>) {
|
clearLines(lines: Array<number>) {
|
||||||
|
@ -191,52 +174,6 @@ export default class GameLogic {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
isTetrominoPositionValid() {
|
|
||||||
let isValid = true;
|
|
||||||
let coord = this.currentObject.getCellsCoordinates(true);
|
|
||||||
for (let i = 0; i < coord.length; i++) {
|
|
||||||
if (coord[i].x >= this.getWidth()
|
|
||||||
|| coord[i].x < 0
|
|
||||||
|| coord[i].y >= this.getHeight()
|
|
||||||
|| coord[i].y < 0
|
|
||||||
|| !this.currentGrid[coord[i].y][coord[i].x].isEmpty) {
|
|
||||||
isValid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
tryMoveTetromino(x: number, y: number) {
|
|
||||||
if (x > 1) x = 1; // Prevent moving from more than one tile
|
|
||||||
if (x < -1) x = -1;
|
|
||||||
if (y > 1) y = 1;
|
|
||||||
if (y < -1) y = -1;
|
|
||||||
if (x !== 0 && y !== 0) y = 0; // Prevent diagonal movement
|
|
||||||
|
|
||||||
this.currentObject.move(x, y);
|
|
||||||
let isValid = this.isTetrominoPositionValid();
|
|
||||||
|
|
||||||
if (!isValid && x !== 0)
|
|
||||||
this.currentObject.move(-x, 0);
|
|
||||||
else if (!isValid && y !== 0) {
|
|
||||||
this.currentObject.move(0, -y);
|
|
||||||
this.freezeTetromino();
|
|
||||||
this.createTetromino();
|
|
||||||
} else
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
tryRotateTetromino() {
|
|
||||||
this.currentObject.rotate(true);
|
|
||||||
if (!this.isTetrominoPositionValid()) {
|
|
||||||
this.currentObject.rotate(false);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewGameTick(level: number) {
|
setNewGameTick(level: number) {
|
||||||
if (level >= GameLogic.levelTicks.length)
|
if (level >= GameLogic.levelTicks.length)
|
||||||
return;
|
return;
|
||||||
|
@ -245,8 +182,15 @@ export default class GameLogic {
|
||||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onFreeze() {
|
||||||
|
this.freezeTetromino();
|
||||||
|
this.createTetromino();
|
||||||
|
}
|
||||||
|
|
||||||
onTick(callback: Function) {
|
onTick(callback: Function) {
|
||||||
this.tryMoveTetromino(0, 1);
|
this.currentObject.tryMove(0, 1,
|
||||||
|
this.currentGrid, this.getWidth(), this.getHeight(),
|
||||||
|
() => this.onFreeze());
|
||||||
callback(this.score, this.level, this.getFinalGrid());
|
callback(this.score, this.level, this.getFinalGrid());
|
||||||
if (this.canLevelUp()) {
|
if (this.canLevelUp()) {
|
||||||
this.level++;
|
this.level++;
|
||||||
|
@ -281,8 +225,10 @@ export default class GameLogic {
|
||||||
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
|
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
|
||||||
if (!this.canUseInput() || !this.isPressedIn)
|
if (!this.canUseInput() || !this.isPressedIn)
|
||||||
return;
|
return;
|
||||||
|
const moved = this.currentObject.tryMove(x, y,
|
||||||
if (this.tryMoveTetromino(x, y)) {
|
this.currentGrid, this.getWidth(), this.getHeight(),
|
||||||
|
() => this.onFreeze());
|
||||||
|
if (moved) {
|
||||||
if (y === 1) {
|
if (y === 1) {
|
||||||
this.score++;
|
this.score++;
|
||||||
callback(this.getFinalGrid(), this.score);
|
callback(this.getFinalGrid(), this.score);
|
||||||
|
@ -301,7 +247,7 @@ export default class GameLogic {
|
||||||
if (!this.canUseInput())
|
if (!this.canUseInput())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.tryRotateTetromino())
|
if (this.currentObject.tryRotate(this.currentGrid, this.getWidth(), this.getHeight()))
|
||||||
callback(this.getFinalGrid());
|
callback(this.getFinalGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -312,15 +258,14 @@ export default class GameLogic {
|
||||||
|
|
||||||
generateNextPieces() {
|
generateNextPieces() {
|
||||||
while (this.nextPieces.length < this.nextPiecesCount) {
|
while (this.nextPieces.length < this.nextPiecesCount) {
|
||||||
let shape = Math.floor(Math.random() * 7);
|
this.nextPieces.push(new Piece(this.colors));
|
||||||
this.nextPieces.push(new Tetromino(shape, this.colors));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createTetromino() {
|
createTetromino() {
|
||||||
this.pressedOut();
|
this.pressedOut();
|
||||||
this.recoverNextPiece();
|
this.recoverNextPiece();
|
||||||
if (!this.isTetrominoPositionValid())
|
if (!this.currentObject.isPositionValid(this.currentGrid, this.getWidth(), this.getHeight()))
|
||||||
this.endGame(false);
|
this.endGame(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
85
screens/Tetris/Piece.js
Normal file
85
screens/Tetris/Piece.js
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import ShapeL from "./Shapes/ShapeL";
|
||||||
|
import ShapeI from "./Shapes/ShapeI";
|
||||||
|
import ShapeJ from "./Shapes/ShapeJ";
|
||||||
|
import ShapeO from "./Shapes/ShapeO";
|
||||||
|
import ShapeS from "./Shapes/ShapeS";
|
||||||
|
import ShapeT from "./Shapes/ShapeT";
|
||||||
|
import ShapeZ from "./Shapes/ShapeZ";
|
||||||
|
|
||||||
|
export default class Piece {
|
||||||
|
|
||||||
|
#shapes = [
|
||||||
|
ShapeL,
|
||||||
|
ShapeI,
|
||||||
|
ShapeJ,
|
||||||
|
ShapeO,
|
||||||
|
ShapeS,
|
||||||
|
ShapeT,
|
||||||
|
ShapeZ,
|
||||||
|
];
|
||||||
|
|
||||||
|
#currentShape: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
this.#currentShape = new this.#shapes[Math.floor(Math.random() * 7)](colors);
|
||||||
|
}
|
||||||
|
|
||||||
|
toGrid(grid: Array<Array<Object>>, isPreview: boolean) {
|
||||||
|
const coord = this.#currentShape.getCellsCoordinates(!isPreview);
|
||||||
|
for (let i = 0; i < coord.length; i++) {
|
||||||
|
grid[coord[i].y][coord[i].x] = {
|
||||||
|
color: this.#currentShape.getColor(),
|
||||||
|
isEmpty: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isPositionValid(grid, width, height) {
|
||||||
|
let isValid = true;
|
||||||
|
const coord = this.#currentShape.getCellsCoordinates(true);
|
||||||
|
for (let i = 0; i < coord.length; i++) {
|
||||||
|
if (coord[i].x >= width
|
||||||
|
|| coord[i].x < 0
|
||||||
|
|| coord[i].y >= height
|
||||||
|
|| coord[i].y < 0
|
||||||
|
|| !grid[coord[i].y][coord[i].x].isEmpty) {
|
||||||
|
isValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryMove(x: number, y: number, grid, width, height, freezeCallback: Function) {
|
||||||
|
if (x > 1) x = 1; // Prevent moving from more than one tile
|
||||||
|
if (x < -1) x = -1;
|
||||||
|
if (y > 1) y = 1;
|
||||||
|
if (y < -1) y = -1;
|
||||||
|
if (x !== 0 && y !== 0) y = 0; // Prevent diagonal movement
|
||||||
|
|
||||||
|
this.#currentShape.move(x, y);
|
||||||
|
let isValid = this.isPositionValid(grid, width, height);
|
||||||
|
|
||||||
|
if (!isValid && x !== 0)
|
||||||
|
this.#currentShape.move(-x, 0);
|
||||||
|
else if (!isValid && y !== 0) {
|
||||||
|
this.#currentShape.move(0, -y);
|
||||||
|
freezeCallback();
|
||||||
|
} else
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tryRotate(grid, width, height) {
|
||||||
|
this.#currentShape.rotate(true);
|
||||||
|
if (!this.isPositionValid(grid, width, height)) {
|
||||||
|
this.#currentShape.rotate(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCoordinates() {
|
||||||
|
return this.#currentShape.getCellsCoordinates(true);
|
||||||
|
}
|
||||||
|
}
|
65
screens/Tetris/Shapes/BaseShape.js
Normal file
65
screens/Tetris/Shapes/BaseShape.js
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class used to represent a BaseShape.
|
||||||
|
* Abstract classes do not exist by default in Javascript: we force it by throwing errors in the constructor
|
||||||
|
* and in methods to implement
|
||||||
|
*/
|
||||||
|
export default class BaseShape {
|
||||||
|
|
||||||
|
#currentShape: Array<Array<number>>;
|
||||||
|
#rotation: number;
|
||||||
|
position: Object;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (this.constructor === BaseShape)
|
||||||
|
throw new Error("Abstract class can't be instantiated");
|
||||||
|
this.#rotation = 0;
|
||||||
|
this.position = {x: 0, y: 0};
|
||||||
|
this.#currentShape = this.getShapes()[this.#rotation];
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
throw new Error("Method 'getColor()' must be implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes(): Array<Array<Array<number>>> {
|
||||||
|
throw new Error("Method 'getShapes()' must be implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentShape() {
|
||||||
|
return this.#currentShape;
|
||||||
|
}
|
||||||
|
|
||||||
|
getCellsCoordinates(isAbsolute: boolean) {
|
||||||
|
let coordinates = [];
|
||||||
|
for (let row = 0; row < this.#currentShape.length; row++) {
|
||||||
|
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
||||||
|
if (this.#currentShape[row][col] === 1)
|
||||||
|
if (isAbsolute)
|
||||||
|
coordinates.push({x: this.position.x + col, y: this.position.y + row});
|
||||||
|
else
|
||||||
|
coordinates.push({x: col, y: row});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coordinates;
|
||||||
|
}
|
||||||
|
|
||||||
|
rotate(isForward: boolean) {
|
||||||
|
if (isForward)
|
||||||
|
this.#rotation++;
|
||||||
|
else
|
||||||
|
this.#rotation--;
|
||||||
|
if (this.#rotation > 3)
|
||||||
|
this.#rotation = 0;
|
||||||
|
else if (this.#rotation < 0)
|
||||||
|
this.#rotation = 3;
|
||||||
|
this.#currentShape = this.getShapes()[this.#rotation];
|
||||||
|
}
|
||||||
|
|
||||||
|
move(x: number, y: number) {
|
||||||
|
this.position.x += x;
|
||||||
|
this.position.y += y;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
47
screens/Tetris/Shapes/ShapeI.js
Normal file
47
screens/Tetris/Shapes/ShapeI.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeI extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisI;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
screens/Tetris/Shapes/ShapeJ.js
Normal file
43
screens/Tetris/Shapes/ShapeJ.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeJ extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisJ;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[1, 0, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
[1, 1, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
screens/Tetris/Shapes/ShapeL.js
Normal file
43
screens/Tetris/Shapes/ShapeL.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeL extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisL;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[0, 0, 1],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[1, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
39
screens/Tetris/Shapes/ShapeO.js
Normal file
39
screens/Tetris/Shapes/ShapeO.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeO extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 4;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisO;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[1, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[1, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[1, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[1, 1],
|
||||||
|
[1, 1],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
screens/Tetris/Shapes/ShapeS.js
Normal file
43
screens/Tetris/Shapes/ShapeS.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeS extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisS;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[0, 1, 1],
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 0, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
[1, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[1, 0, 0],
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
screens/Tetris/Shapes/ShapeT.js
Normal file
43
screens/Tetris/Shapes/ShapeT.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeT extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisT;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
43
screens/Tetris/Shapes/ShapeZ.js
Normal file
43
screens/Tetris/Shapes/ShapeZ.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import BaseShape from "./BaseShape";
|
||||||
|
|
||||||
|
export default class ShapeZ extends BaseShape {
|
||||||
|
|
||||||
|
#colors: Object;
|
||||||
|
|
||||||
|
constructor(colors: Object) {
|
||||||
|
super();
|
||||||
|
this.position.x = 3;
|
||||||
|
this.#colors = colors;
|
||||||
|
}
|
||||||
|
|
||||||
|
getColor(): string {
|
||||||
|
return this.#colors.tetrisZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
getShapes() {
|
||||||
|
return [
|
||||||
|
[
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 1],
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 1, 0],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 0, 0],
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
[0, 1, 0],
|
||||||
|
[1, 1, 0],
|
||||||
|
[1, 0, 0],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -253,7 +253,7 @@ class TetrisScreen extends React.Component<Props, State> {
|
||||||
right: 5,
|
right: 5,
|
||||||
}}>
|
}}>
|
||||||
<Preview
|
<Preview
|
||||||
next={this.logic.getNextPieces()}
|
next={this.logic.getNextPiecesPreviews()}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={{
|
<View style={{
|
||||||
|
|
|
@ -1,230 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
export default class Tetromino {
|
|
||||||
|
|
||||||
static types = {
|
|
||||||
'I': 0,
|
|
||||||
'O': 1,
|
|
||||||
'T': 2,
|
|
||||||
'S': 3,
|
|
||||||
'Z': 4,
|
|
||||||
'J': 5,
|
|
||||||
'L': 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
static shapes = [
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
[1, 1, 1, 1],
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1],
|
|
||||||
[1, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[1, 1, 1],
|
|
||||||
[0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 1],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
[0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 0, 0],
|
|
||||||
[1, 1, 1],
|
|
||||||
[0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 1],
|
|
||||||
[1, 1, 1],
|
|
||||||
[0, 0, 0],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1],
|
|
||||||
[1, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
[0, 0, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 1],
|
|
||||||
[0, 1, 1],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 1],
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
[1, 1, 1, 1],
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1],
|
|
||||||
[1, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 1, 1],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
[1, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 1, 1],
|
|
||||||
[0, 0, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 0, 0],
|
|
||||||
[1, 1, 1],
|
|
||||||
[1, 0, 0],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1],
|
|
||||||
[1, 1],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 0, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
[1, 0, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
[1, 1, 0],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
[1, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
[0, 1, 0],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
static colors: Object;
|
|
||||||
|
|
||||||
currentType: number;
|
|
||||||
currentShape: Object;
|
|
||||||
currentRotation: number;
|
|
||||||
position: Object;
|
|
||||||
colors: Object;
|
|
||||||
|
|
||||||
constructor(type: number, colors: Object) {
|
|
||||||
this.currentType = type;
|
|
||||||
this.currentRotation = 0;
|
|
||||||
this.currentShape = Tetromino.shapes[this.currentRotation][type];
|
|
||||||
this.position = {x: 0, y: 0};
|
|
||||||
if (this.currentType === Tetromino.types.O)
|
|
||||||
this.position.x = 4;
|
|
||||||
else
|
|
||||||
this.position.x = 3;
|
|
||||||
this.colors = colors;
|
|
||||||
Tetromino.colors = [
|
|
||||||
colors.tetrisI,
|
|
||||||
colors.tetrisO,
|
|
||||||
colors.tetrisT,
|
|
||||||
colors.tetrisS,
|
|
||||||
colors.tetrisZ,
|
|
||||||
colors.tetrisJ,
|
|
||||||
colors.tetrisL,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
getColor() {
|
|
||||||
return Tetromino.colors[this.currentType];
|
|
||||||
}
|
|
||||||
|
|
||||||
getCellsCoordinates(isAbsolute: boolean) {
|
|
||||||
let coordinates = [];
|
|
||||||
for (let row = 0; row < this.currentShape.length; row++) {
|
|
||||||
for (let col = 0; col < this.currentShape[row].length; col++) {
|
|
||||||
if (this.currentShape[row][col] === 1)
|
|
||||||
if (isAbsolute)
|
|
||||||
coordinates.push({x: this.position.x + col, y: this.position.y + row});
|
|
||||||
else
|
|
||||||
coordinates.push({x: col, y: row});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return coordinates;
|
|
||||||
}
|
|
||||||
|
|
||||||
rotate(isForward: boolean) {
|
|
||||||
if (isForward)
|
|
||||||
this.currentRotation++;
|
|
||||||
else
|
|
||||||
this.currentRotation--;
|
|
||||||
if (this.currentRotation > 3)
|
|
||||||
this.currentRotation = 0;
|
|
||||||
else if (this.currentRotation < 0)
|
|
||||||
this.currentRotation = 3;
|
|
||||||
this.currentShape = Tetromino.shapes[this.currentRotation][this.currentType];
|
|
||||||
}
|
|
||||||
|
|
||||||
move(x: number, y: number) {
|
|
||||||
this.position.x += x;
|
|
||||||
this.position.y += y;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
104
screens/Tetris/__tests__/Tetromino.test.js
Normal file
104
screens/Tetris/__tests__/Tetromino.test.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import React from 'react';
|
||||||
|
import BaseShape from "../Shapes/BaseShape";
|
||||||
|
import ShapeI from "../Shapes/ShapeI";
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
tetrisI: '#000001',
|
||||||
|
tetrisO: '#000002',
|
||||||
|
tetrisT: '#000003',
|
||||||
|
tetrisS: '#000004',
|
||||||
|
tetrisZ: '#000005',
|
||||||
|
tetrisJ: '#000006',
|
||||||
|
tetrisL: '#000007',
|
||||||
|
};
|
||||||
|
|
||||||
|
test('constructor', () => {
|
||||||
|
expect(() => new BaseShape()).toThrow(Error);
|
||||||
|
|
||||||
|
let T = new ShapeI(colors);
|
||||||
|
expect(T.position.y).toBe(0);
|
||||||
|
expect(T.position.x).toBe(3);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[0]);
|
||||||
|
expect(T.getColor()).toBe(colors.tetrisI);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("move", () => {
|
||||||
|
let T = new ShapeI(colors);
|
||||||
|
T.move(0, 1);
|
||||||
|
expect(T.position.x).toBe(3);
|
||||||
|
expect(T.position.y).toBe(1);
|
||||||
|
T.move(1, 0);
|
||||||
|
expect(T.position.x).toBe(4);
|
||||||
|
expect(T.position.y).toBe(1);
|
||||||
|
T.move(1, 1);
|
||||||
|
expect(T.position.x).toBe(5);
|
||||||
|
expect(T.position.y).toBe(2);
|
||||||
|
T.move(2, 2);
|
||||||
|
expect(T.position.x).toBe(7);
|
||||||
|
expect(T.position.y).toBe(4);
|
||||||
|
T.move(-1, -1);
|
||||||
|
expect(T.position.x).toBe(6);
|
||||||
|
expect(T.position.y).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('rotate', () => {
|
||||||
|
let T = new ShapeI(colors);
|
||||||
|
T.rotate(true);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[1]);
|
||||||
|
T.rotate(true);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[2]);
|
||||||
|
T.rotate(true);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[3]);
|
||||||
|
T.rotate(true);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[0]);
|
||||||
|
T.rotate(false);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[3]);
|
||||||
|
T.rotate(false);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[2]);
|
||||||
|
T.rotate(false);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[1]);
|
||||||
|
T.rotate(false);
|
||||||
|
expect(T.getCurrentShape()).toStrictEqual(T.getShapes()[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getCellsCoordinates', () => {
|
||||||
|
let T = new ShapeI(colors);
|
||||||
|
expect(T.getCellsCoordinates(false)).toStrictEqual([
|
||||||
|
{x: 0, y: 1},
|
||||||
|
{x: 1, y: 1},
|
||||||
|
{x: 2, y: 1},
|
||||||
|
{x: 3, y: 1},
|
||||||
|
]);
|
||||||
|
expect(T.getCellsCoordinates(true)).toStrictEqual([
|
||||||
|
{x: 3, y: 1},
|
||||||
|
{x: 4, y: 1},
|
||||||
|
{x: 5, y: 1},
|
||||||
|
{x: 6, y: 1},
|
||||||
|
]);
|
||||||
|
T.move(1, 1);
|
||||||
|
expect(T.getCellsCoordinates(false)).toStrictEqual([
|
||||||
|
{x: 0, y: 1},
|
||||||
|
{x: 1, y: 1},
|
||||||
|
{x: 2, y: 1},
|
||||||
|
{x: 3, y: 1},
|
||||||
|
]);
|
||||||
|
expect(T.getCellsCoordinates(true)).toStrictEqual([
|
||||||
|
{x: 4, y: 2},
|
||||||
|
{x: 5, y: 2},
|
||||||
|
{x: 6, y: 2},
|
||||||
|
{x: 7, y: 2},
|
||||||
|
]);
|
||||||
|
T.rotate(true);
|
||||||
|
expect(T.getCellsCoordinates(false)).toStrictEqual([
|
||||||
|
{x: 2, y: 0},
|
||||||
|
{x: 2, y: 1},
|
||||||
|
{x: 2, y: 2},
|
||||||
|
{x: 2, y: 3},
|
||||||
|
]);
|
||||||
|
expect(T.getCellsCoordinates(true)).toStrictEqual([
|
||||||
|
{x: 6, y: 1},
|
||||||
|
{x: 6, y: 2},
|
||||||
|
{x: 6, y: 3},
|
||||||
|
{x: 6, y: 4},
|
||||||
|
]);
|
||||||
|
});
|
Loading…
Reference in a new issue