forked from vergnet/application-amicale
212 lines
5.7 KiB
TypeScript
212 lines
5.7 KiB
TypeScript
/*
|
|
* Copyright (c) 2019 - 2020 Arnaud Vergnet.
|
|
*
|
|
* This file is part of Campus INSAT.
|
|
*
|
|
* Campus INSAT is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Campus INSAT is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import 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';
|
|
import type { CoordinatesType } from '../Shapes/BaseShape';
|
|
import BaseShape from '../Shapes/BaseShape';
|
|
import type { GridType } from '../components/GridComponent';
|
|
|
|
/**
|
|
* Class used as an abstraction layer for shapes.
|
|
* Use this class to manipulate pieces rather than Shapes directly
|
|
*
|
|
*/
|
|
export default class Piece {
|
|
shapes = [ShapeL, ShapeI, ShapeJ, ShapeO, ShapeS, ShapeT, ShapeZ];
|
|
|
|
currentShape: BaseShape;
|
|
|
|
theme: ReactNativePaper.Theme;
|
|
|
|
/**
|
|
* Initializes this piece's color and shape
|
|
*
|
|
* @param theme Object containing current theme
|
|
*/
|
|
constructor(theme: ReactNativePaper.Theme) {
|
|
this.currentShape = this.getRandomShape(theme);
|
|
this.theme = theme;
|
|
}
|
|
|
|
/**
|
|
* Gets a random shape object
|
|
*
|
|
* @param theme Object containing current theme
|
|
*/
|
|
getRandomShape(theme: ReactNativePaper.Theme): BaseShape {
|
|
return new this.shapes[Math.floor(Math.random() * 7)](theme);
|
|
}
|
|
|
|
/**
|
|
* Removes the piece from the given grid
|
|
*
|
|
* @param grid The grid to remove the piece from
|
|
*/
|
|
removeFromGrid(grid: GridType) {
|
|
const pos: Array<CoordinatesType> = this.currentShape.getCellsCoordinates(
|
|
true
|
|
);
|
|
pos.forEach((coordinates: CoordinatesType) => {
|
|
grid[coordinates.y][coordinates.x] = {
|
|
color: this.theme.colors.tetrisBackground,
|
|
isEmpty: true,
|
|
key: grid[coordinates.y][coordinates.x].key,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Adds this piece to the given grid
|
|
*
|
|
* @param grid The grid to add the piece to
|
|
* @param isPreview Should we use this piece's current position to determine the cells?
|
|
*/
|
|
toGrid(grid: GridType, isPreview: boolean) {
|
|
const pos: Array<CoordinatesType> = this.currentShape.getCellsCoordinates(
|
|
!isPreview
|
|
);
|
|
pos.forEach((coordinates: CoordinatesType) => {
|
|
grid[coordinates.y][coordinates.x] = {
|
|
color: this.currentShape.getColor(),
|
|
isEmpty: false,
|
|
key: grid[coordinates.y][coordinates.x].key,
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Checks if the piece's current position is valid
|
|
*
|
|
* @param grid The current game grid
|
|
* @param width The grid's width
|
|
* @param height The grid's height
|
|
* @return {boolean} If the position is valid
|
|
*/
|
|
isPositionValid(grid: GridType, width: number, height: number): boolean {
|
|
let isValid = true;
|
|
const pos: Array<CoordinatesType> = this.currentShape.getCellsCoordinates(
|
|
true
|
|
);
|
|
for (let i = 0; i < pos.length; i += 1) {
|
|
if (
|
|
pos[i].x >= width ||
|
|
pos[i].x < 0 ||
|
|
pos[i].y >= height ||
|
|
pos[i].y < 0 ||
|
|
!grid[pos[i].y][pos[i].x].isEmpty
|
|
) {
|
|
isValid = false;
|
|
break;
|
|
}
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Tries to move the piece by the given offset on the given grid
|
|
*
|
|
* @param x Position X offset
|
|
* @param y Position Y offset
|
|
* @param grid The grid to move the piece on
|
|
* @param width The grid's width
|
|
* @param height The grid's height
|
|
* @param freezeCallback Callback to use if the piece should freeze itself
|
|
* @return {boolean} True if the move was valid, false otherwise
|
|
*/
|
|
tryMove(
|
|
x: number,
|
|
y: number,
|
|
grid: GridType,
|
|
width: number,
|
|
height: number,
|
|
freezeCallback: () => void
|
|
): boolean {
|
|
let newX = x;
|
|
let newY = y;
|
|
if (x > 1) {
|
|
newX = 1;
|
|
} // Prevent moving from more than one tile
|
|
if (x < -1) {
|
|
newX = -1;
|
|
}
|
|
if (y > 1) {
|
|
newY = 1;
|
|
}
|
|
if (y < -1) {
|
|
newY = -1;
|
|
}
|
|
if (x !== 0 && y !== 0) {
|
|
newY = 0;
|
|
} // Prevent diagonal movement
|
|
|
|
this.removeFromGrid(grid);
|
|
this.currentShape.move(newX, newY);
|
|
const isValid = this.isPositionValid(grid, width, height);
|
|
|
|
if (!isValid) {
|
|
this.currentShape.move(-newX, -newY);
|
|
}
|
|
|
|
const shouldFreeze = !isValid && newY !== 0;
|
|
this.toGrid(grid, false);
|
|
if (shouldFreeze) {
|
|
freezeCallback();
|
|
}
|
|
return isValid;
|
|
}
|
|
|
|
/**
|
|
* Tries to rotate the piece
|
|
*
|
|
* @param grid The grid to rotate the piece on
|
|
* @param width The grid's width
|
|
* @param height The grid's height
|
|
* @return {boolean} True if the rotation was valid, false otherwise
|
|
*/
|
|
tryRotate(grid: GridType, width: number, height: number): boolean {
|
|
this.removeFromGrid(grid);
|
|
this.currentShape.rotate(true);
|
|
if (!this.isPositionValid(grid, width, height)) {
|
|
this.currentShape.rotate(false);
|
|
this.toGrid(grid, false);
|
|
return false;
|
|
}
|
|
this.toGrid(grid, false);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Gets this piece used cells coordinates
|
|
*
|
|
* @return {Array<CoordinatesType>} An array of coordinates
|
|
*/
|
|
getCoordinates(): Array<CoordinatesType> {
|
|
return this.currentShape.getCellsCoordinates(true);
|
|
}
|
|
|
|
getCurrentShape(): BaseShape {
|
|
return this.currentShape;
|
|
}
|
|
}
|