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
|
||||
|
||||
import Tetromino from "./Tetromino";
|
||||
import Piece from "./Piece";
|
||||
|
||||
export default class GameLogic {
|
||||
|
||||
|
@ -28,7 +28,7 @@ export default class GameLogic {
|
|||
score: number;
|
||||
level: number;
|
||||
|
||||
currentObject: Tetromino;
|
||||
currentObject: Piece;
|
||||
|
||||
gameTick: number;
|
||||
gameTickInterval: IntervalID;
|
||||
|
@ -39,7 +39,7 @@ export default class GameLogic {
|
|||
autoRepeatActivationDelay: number;
|
||||
autoRepeatDelay: number;
|
||||
|
||||
nextPieces: Array<Tetromino>;
|
||||
nextPieces: Array<Piece>;
|
||||
nextPiecesCount: number;
|
||||
|
||||
onTick: Function;
|
||||
|
@ -62,12 +62,11 @@ export default class GameLogic {
|
|||
this.nextPiecesCount = 3;
|
||||
}
|
||||
|
||||
getNextPieces() {
|
||||
getNextPiecesPreviews() {
|
||||
let finalArray = [];
|
||||
for (let i = 0; i < this.nextPieces.length; i++) {
|
||||
finalArray.push(this.getEmptyGrid(4, 4));
|
||||
let coord = this.nextPieces[i].getCellsCoordinates(false);
|
||||
this.tetrominoToGrid(this.nextPieces[i], coord, finalArray[i]);
|
||||
this.nextPieces[i].toGrid(finalArray[i], true);
|
||||
}
|
||||
|
||||
return finalArray;
|
||||
|
@ -113,14 +112,8 @@ export default class GameLogic {
|
|||
}
|
||||
|
||||
getFinalGrid() {
|
||||
let coord = this.currentObject.getCellsCoordinates(true);
|
||||
let finalGrid = this.getGridCopy();
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
finalGrid[coord[i].y][coord[i].x] = {
|
||||
color: this.currentObject.getColor(),
|
||||
isEmpty: false,
|
||||
};
|
||||
}
|
||||
this.currentObject.toGrid(finalGrid, false);
|
||||
return finalGrid;
|
||||
}
|
||||
|
||||
|
@ -137,19 +130,9 @@ export default class GameLogic {
|
|||
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() {
|
||||
let coord = this.currentObject.getCellsCoordinates(true);
|
||||
this.tetrominoToGrid(this.currentObject, coord, this.currentGrid);
|
||||
this.clearLines(this.getLinesToClear(coord));
|
||||
this.currentObject.toGrid(this.currentGrid, false);
|
||||
this.clearLines(this.getLinesToClear(this.currentObject.getCoordinates()));
|
||||
}
|
||||
|
||||
clearLines(lines: Array<number>) {
|
||||
|
@ -191,52 +174,6 @@ export default class GameLogic {
|
|||
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) {
|
||||
if (level >= GameLogic.levelTicks.length)
|
||||
return;
|
||||
|
@ -245,8 +182,15 @@ export default class GameLogic {
|
|||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||
}
|
||||
|
||||
onFreeze() {
|
||||
this.freezeTetromino();
|
||||
this.createTetromino();
|
||||
}
|
||||
|
||||
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());
|
||||
if (this.canLevelUp()) {
|
||||
this.level++;
|
||||
|
@ -281,8 +225,10 @@ export default class GameLogic {
|
|||
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
|
||||
if (!this.canUseInput() || !this.isPressedIn)
|
||||
return;
|
||||
|
||||
if (this.tryMoveTetromino(x, y)) {
|
||||
const moved = this.currentObject.tryMove(x, y,
|
||||
this.currentGrid, this.getWidth(), this.getHeight(),
|
||||
() => this.onFreeze());
|
||||
if (moved) {
|
||||
if (y === 1) {
|
||||
this.score++;
|
||||
callback(this.getFinalGrid(), this.score);
|
||||
|
@ -301,7 +247,7 @@ export default class GameLogic {
|
|||
if (!this.canUseInput())
|
||||
return;
|
||||
|
||||
if (this.tryRotateTetromino())
|
||||
if (this.currentObject.tryRotate(this.currentGrid, this.getWidth(), this.getHeight()))
|
||||
callback(this.getFinalGrid());
|
||||
}
|
||||
|
||||
|
@ -312,15 +258,14 @@ export default class GameLogic {
|
|||
|
||||
generateNextPieces() {
|
||||
while (this.nextPieces.length < this.nextPiecesCount) {
|
||||
let shape = Math.floor(Math.random() * 7);
|
||||
this.nextPieces.push(new Tetromino(shape, this.colors));
|
||||
this.nextPieces.push(new Piece(this.colors));
|
||||
}
|
||||
}
|
||||
|
||||
createTetromino() {
|
||||
this.pressedOut();
|
||||
this.recoverNextPiece();
|
||||
if (!this.isTetrominoPositionValid())
|
||||
if (!this.currentObject.isPositionValid(this.currentGrid, this.getWidth(), this.getHeight()))
|
||||
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,
|
||||
}}>
|
||||
<Preview
|
||||
next={this.logic.getNextPieces()}
|
||||
next={this.logic.getNextPiecesPreviews()}
|
||||
/>
|
||||
</View>
|
||||
<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