forked from vergnet/application-amicale
Updated doc and used private class members
This commit is contained in:
parent
931d7b0fe6
commit
cded72137e
5 changed files with 313 additions and 119 deletions
|
@ -17,218 +17,221 @@ export default class GameLogic {
|
|||
100,
|
||||
];
|
||||
|
||||
scoreManager: ScoreManager;
|
||||
gridManager: GridManager;
|
||||
#scoreManager: ScoreManager;
|
||||
#gridManager: GridManager;
|
||||
|
||||
height: number;
|
||||
width: number;
|
||||
#height: number;
|
||||
#width: number;
|
||||
|
||||
gameRunning: boolean;
|
||||
gamePaused: boolean;
|
||||
gameTime: number;
|
||||
#gameRunning: boolean;
|
||||
#gamePaused: boolean;
|
||||
#gameTime: number;
|
||||
|
||||
currentObject: Piece;
|
||||
#currentObject: Piece;
|
||||
|
||||
gameTick: number;
|
||||
gameTickInterval: IntervalID;
|
||||
gameTimeInterval: IntervalID;
|
||||
#gameTick: number;
|
||||
#gameTickInterval: IntervalID;
|
||||
#gameTimeInterval: IntervalID;
|
||||
|
||||
pressInInterval: TimeoutID;
|
||||
isPressedIn: boolean;
|
||||
autoRepeatActivationDelay: number;
|
||||
autoRepeatDelay: number;
|
||||
#pressInInterval: TimeoutID;
|
||||
#isPressedIn: boolean;
|
||||
#autoRepeatActivationDelay: number;
|
||||
#autoRepeatDelay: number;
|
||||
|
||||
nextPieces: Array<Piece>;
|
||||
nextPiecesCount: number;
|
||||
#nextPieces: Array<Piece>;
|
||||
#nextPiecesCount: number;
|
||||
|
||||
onTick: Function;
|
||||
onClock: Function;
|
||||
#onTick: Function;
|
||||
#onClock: Function;
|
||||
endCallback: Function;
|
||||
|
||||
colors: Object;
|
||||
#colors: Object;
|
||||
|
||||
constructor(height: number, width: number, colors: Object) {
|
||||
this.height = height;
|
||||
this.width = width;
|
||||
this.gameRunning = false;
|
||||
this.gamePaused = false;
|
||||
this.colors = colors;
|
||||
this.autoRepeatActivationDelay = 300;
|
||||
this.autoRepeatDelay = 50;
|
||||
this.nextPieces = [];
|
||||
this.nextPiecesCount = 3;
|
||||
this.scoreManager = new ScoreManager();
|
||||
this.gridManager = new GridManager(this.getWidth(), this.getHeight(), this.colors);
|
||||
this.#height = height;
|
||||
this.#width = width;
|
||||
this.#gameRunning = false;
|
||||
this.#gamePaused = false;
|
||||
this.#colors = colors;
|
||||
this.#autoRepeatActivationDelay = 300;
|
||||
this.#autoRepeatDelay = 50;
|
||||
this.#nextPieces = [];
|
||||
this.#nextPiecesCount = 3;
|
||||
this.#scoreManager = new ScoreManager();
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||
}
|
||||
|
||||
getHeight(): number {
|
||||
return this.height;
|
||||
return this.#height;
|
||||
}
|
||||
|
||||
getWidth(): number {
|
||||
return this.width;
|
||||
return this.#width;
|
||||
}
|
||||
|
||||
getCurrentGrid() {
|
||||
return this.gridManager.getCurrentGrid();
|
||||
return this.#gridManager.getCurrentGrid();
|
||||
}
|
||||
|
||||
isGameRunning(): boolean {
|
||||
return this.gameRunning;
|
||||
return this.#gameRunning;
|
||||
}
|
||||
|
||||
isGamePaused(): boolean {
|
||||
return this.gamePaused;
|
||||
return this.#gamePaused;
|
||||
}
|
||||
|
||||
onFreeze() {
|
||||
this.gridManager.freezeTetromino(this.currentObject, this.scoreManager);
|
||||
this.#gridManager.freezeTetromino(this.#currentObject, this.#scoreManager);
|
||||
this.createTetromino();
|
||||
}
|
||||
|
||||
setNewGameTick(level: number) {
|
||||
if (level >= GameLogic.levelTicks.length)
|
||||
return;
|
||||
this.gameTick = GameLogic.levelTicks[level];
|
||||
clearInterval(this.gameTickInterval);
|
||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||
this.#gameTick = GameLogic.levelTicks[level];
|
||||
clearInterval(this.#gameTickInterval);
|
||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||
}
|
||||
|
||||
onTick(callback: Function) {
|
||||
this.currentObject.tryMove(0, 1,
|
||||
this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||
this.#currentObject.tryMove(0, 1,
|
||||
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||
() => this.onFreeze());
|
||||
callback(
|
||||
this.scoreManager.getScore(),
|
||||
this.scoreManager.getLevel(),
|
||||
this.gridManager.getCurrentGrid());
|
||||
if (this.scoreManager.canLevelUp())
|
||||
this.setNewGameTick(this.scoreManager.getLevel());
|
||||
this.#scoreManager.getScore(),
|
||||
this.#scoreManager.getLevel(),
|
||||
this.#gridManager.getCurrentGrid());
|
||||
if (this.#scoreManager.canLevelUp())
|
||||
this.setNewGameTick(this.#scoreManager.getLevel());
|
||||
}
|
||||
|
||||
onClock(callback: Function) {
|
||||
this.gameTime++;
|
||||
callback(this.gameTime);
|
||||
this.#gameTime++;
|
||||
callback(this.#gameTime);
|
||||
}
|
||||
|
||||
canUseInput() {
|
||||
return this.gameRunning && !this.gamePaused
|
||||
return this.#gameRunning && !this.#gamePaused
|
||||
}
|
||||
|
||||
rightPressed(callback: Function) {
|
||||
this.isPressedIn = true;
|
||||
this.#isPressedIn = true;
|
||||
this.movePressedRepeat(true, callback, 1, 0);
|
||||
}
|
||||
|
||||
leftPressedIn(callback: Function) {
|
||||
this.isPressedIn = true;
|
||||
this.#isPressedIn = true;
|
||||
this.movePressedRepeat(true, callback, -1, 0);
|
||||
}
|
||||
|
||||
downPressedIn(callback: Function) {
|
||||
this.isPressedIn = true;
|
||||
this.#isPressedIn = true;
|
||||
this.movePressedRepeat(true, callback, 0, 1);
|
||||
}
|
||||
|
||||
movePressedRepeat(isInitial: boolean, callback: Function, x: number, y: number) {
|
||||
if (!this.canUseInput() || !this.isPressedIn)
|
||||
if (!this.canUseInput() || !this.#isPressedIn)
|
||||
return;
|
||||
const moved = this.currentObject.tryMove(x, y,
|
||||
this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||
const moved = this.#currentObject.tryMove(x, y,
|
||||
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||
() => this.onFreeze());
|
||||
if (moved) {
|
||||
if (y === 1) {
|
||||
this.scoreManager.incrementScore();
|
||||
callback(this.gridManager.getCurrentGrid(), this.scoreManager.getScore());
|
||||
this.#scoreManager.incrementScore();
|
||||
callback(this.#gridManager.getCurrentGrid(), this.#scoreManager.getScore());
|
||||
} else
|
||||
callback(this.gridManager.getCurrentGrid());
|
||||
callback(this.#gridManager.getCurrentGrid());
|
||||
}
|
||||
this.pressInInterval = setTimeout(() => this.movePressedRepeat(false, callback, x, y), isInitial ? this.autoRepeatActivationDelay : this.autoRepeatDelay);
|
||||
this.#pressInInterval = setTimeout(() =>
|
||||
this.movePressedRepeat(false, callback, x, y),
|
||||
isInitial ? this.#autoRepeatActivationDelay : this.#autoRepeatDelay
|
||||
);
|
||||
}
|
||||
|
||||
pressedOut() {
|
||||
this.isPressedIn = false;
|
||||
clearTimeout(this.pressInInterval);
|
||||
this.#isPressedIn = false;
|
||||
clearTimeout(this.#pressInInterval);
|
||||
}
|
||||
|
||||
rotatePressed(callback: Function) {
|
||||
if (!this.canUseInput())
|
||||
return;
|
||||
|
||||
if (this.currentObject.tryRotate(this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
||||
callback(this.gridManager.getCurrentGrid());
|
||||
if (this.#currentObject.tryRotate(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
||||
callback(this.#gridManager.getCurrentGrid());
|
||||
}
|
||||
|
||||
getNextPiecesPreviews() {
|
||||
let finalArray = [];
|
||||
for (let i = 0; i < this.nextPieces.length; i++) {
|
||||
finalArray.push(this.gridManager.getEmptyGrid(4, 4));
|
||||
this.nextPieces[i].toGrid(finalArray[i], true);
|
||||
for (let i = 0; i < this.#nextPieces.length; i++) {
|
||||
finalArray.push(this.#gridManager.getEmptyGrid(4, 4));
|
||||
this.#nextPieces[i].toGrid(finalArray[i], true);
|
||||
}
|
||||
|
||||
return finalArray;
|
||||
}
|
||||
|
||||
recoverNextPiece() {
|
||||
this.currentObject = this.nextPieces.shift();
|
||||
this.#currentObject = this.#nextPieces.shift();
|
||||
this.generateNextPieces();
|
||||
}
|
||||
|
||||
generateNextPieces() {
|
||||
while (this.nextPieces.length < this.nextPiecesCount) {
|
||||
this.nextPieces.push(new Piece(this.colors));
|
||||
while (this.#nextPieces.length < this.#nextPiecesCount) {
|
||||
this.#nextPieces.push(new Piece(this.#colors));
|
||||
}
|
||||
}
|
||||
|
||||
createTetromino() {
|
||||
this.pressedOut();
|
||||
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);
|
||||
}
|
||||
|
||||
togglePause() {
|
||||
if (!this.gameRunning)
|
||||
if (!this.#gameRunning)
|
||||
return;
|
||||
this.gamePaused = !this.gamePaused;
|
||||
if (this.gamePaused) {
|
||||
clearInterval(this.gameTickInterval);
|
||||
clearInterval(this.gameTimeInterval);
|
||||
this.#gamePaused = !this.#gamePaused;
|
||||
if (this.#gamePaused) {
|
||||
clearInterval(this.#gameTickInterval);
|
||||
clearInterval(this.#gameTimeInterval);
|
||||
} else {
|
||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||
this.gameTimeInterval = setInterval(this.onClock, 1000);
|
||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
endGame(isRestart: boolean) {
|
||||
this.gameRunning = false;
|
||||
this.gamePaused = false;
|
||||
clearInterval(this.gameTickInterval);
|
||||
clearInterval(this.gameTimeInterval);
|
||||
this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
|
||||
this.#gameRunning = false;
|
||||
this.#gamePaused = false;
|
||||
clearInterval(this.#gameTickInterval);
|
||||
clearInterval(this.#gameTimeInterval);
|
||||
this.endCallback(this.#gameTime, this.#scoreManager.getScore(), isRestart);
|
||||
}
|
||||
|
||||
startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
|
||||
if (this.gameRunning)
|
||||
if (this.#gameRunning)
|
||||
this.endGame(true);
|
||||
this.gameRunning = true;
|
||||
this.gamePaused = false;
|
||||
this.gameTime = 0;
|
||||
this.scoreManager = new ScoreManager();
|
||||
this.gameTick = GameLogic.levelTicks[this.scoreManager.getLevel()];
|
||||
this.gridManager = new GridManager(this.getWidth(), this.getHeight(), this.colors);
|
||||
this.nextPieces = [];
|
||||
this.#gameRunning = true;
|
||||
this.#gamePaused = false;
|
||||
this.#gameTime = 0;
|
||||
this.#scoreManager = new ScoreManager();
|
||||
this.#gameTick = GameLogic.levelTicks[this.#scoreManager.getLevel()];
|
||||
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||
this.#nextPieces = [];
|
||||
this.generateNextPieces();
|
||||
this.createTetromino();
|
||||
tickCallback(
|
||||
this.scoreManager.getScore(),
|
||||
this.scoreManager.getLevel(),
|
||||
this.gridManager.getCurrentGrid());
|
||||
clockCallback(this.gameTime);
|
||||
this.onTick = this.onTick.bind(this, tickCallback);
|
||||
this.onClock = this.onClock.bind(this, clockCallback);
|
||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
||||
this.gameTimeInterval = setInterval(this.onClock, 1000);
|
||||
this.#scoreManager.getScore(),
|
||||
this.#scoreManager.getLevel(),
|
||||
this.#gridManager.getCurrentGrid());
|
||||
clockCallback(this.#gameTime);
|
||||
this.#onTick = this.onTick.bind(this, tickCallback);
|
||||
this.#onClock = this.onClock.bind(this, clockCallback);
|
||||
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
||||
this.endCallback = endCallback;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,24 +2,49 @@
|
|||
|
||||
import Piece from "./Piece";
|
||||
import ScoreManager from "./ScoreManager";
|
||||
import type {coordinates} from './Shapes/BaseShape';
|
||||
|
||||
export type grid = Array<Array<{color: string, isEmpty: boolean}>>;
|
||||
|
||||
export type cell = {color: string, isEmpty: boolean};
|
||||
export type grid = Array<Array<cell>>;
|
||||
|
||||
/**
|
||||
* Class used to manage the game grid
|
||||
*
|
||||
*/
|
||||
export default class GridManager {
|
||||
|
||||
#currentGrid: grid;
|
||||
#colors: Object;
|
||||
|
||||
/**
|
||||
* Initializes a grid of the given size
|
||||
*
|
||||
* @param width The grid width
|
||||
* @param height The grid height
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
constructor(width: number, height: number, colors: Object) {
|
||||
this.#colors = colors;
|
||||
this.#currentGrid = this.getEmptyGrid(height, width);
|
||||
}
|
||||
|
||||
getCurrentGrid() {
|
||||
/**
|
||||
* Get the current grid
|
||||
*
|
||||
* @return {grid} The current grid
|
||||
*/
|
||||
getCurrentGrid(): grid {
|
||||
return this.#currentGrid;
|
||||
}
|
||||
|
||||
getEmptyLine(width: number) {
|
||||
/**
|
||||
* Get a new empty grid line of the given size
|
||||
*
|
||||
* @param width The line size
|
||||
* @return {Array<cell>}
|
||||
*/
|
||||
getEmptyLine(width: number): Array<cell> {
|
||||
let line = [];
|
||||
for (let col = 0; col < width; col++) {
|
||||
line.push({
|
||||
|
@ -30,7 +55,14 @@ export default class GridManager {
|
|||
return line;
|
||||
}
|
||||
|
||||
getEmptyGrid(height: number, width: number) {
|
||||
/**
|
||||
* Gets a new empty grid
|
||||
*
|
||||
* @param width The grid width
|
||||
* @param height The grid height
|
||||
* @return {grid} A new empty grid
|
||||
*/
|
||||
getEmptyGrid(height: number, width: number): grid {
|
||||
let grid = [];
|
||||
for (let row = 0; row < height; row++) {
|
||||
grid.push(this.getEmptyLine(width));
|
||||
|
@ -38,6 +70,13 @@ export default class GridManager {
|
|||
return grid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given lines from the grid,
|
||||
* shifts down every line on top and adds new empty lines on top.
|
||||
*
|
||||
* @param lines An array of line numbers to remove
|
||||
* @param scoreManager A reference to the score manager
|
||||
*/
|
||||
clearLines(lines: Array<number>, scoreManager: ScoreManager) {
|
||||
lines.sort();
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
|
@ -47,7 +86,14 @@ export default class GridManager {
|
|||
scoreManager.addLinesRemovedPoints(lines.length);
|
||||
}
|
||||
|
||||
getLinesToClear(coord: Object) {
|
||||
/**
|
||||
* Gets the lines to clear around the given piece's coordinates.
|
||||
* The piece's coordinates are used for optimization and to prevent checking the whole grid.
|
||||
*
|
||||
* @param coord The piece's coordinates to check lines at
|
||||
* @return {Array<number>} An array containing the line numbers to clear
|
||||
*/
|
||||
getLinesToClear(coord: Array<coordinates>): Array<number> {
|
||||
let rows = [];
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
let isLineFull = true;
|
||||
|
@ -63,6 +109,12 @@ export default class GridManager {
|
|||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Freezes the given piece to the grid
|
||||
*
|
||||
* @param currentObject The piece to freeze
|
||||
* @param scoreManager A reference to the score manager
|
||||
*/
|
||||
freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
|
||||
this.clearLines(this.getLinesToClear(currentObject.getCoordinates()), scoreManager);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,14 @@ import ShapeO from "./Shapes/ShapeO";
|
|||
import ShapeS from "./Shapes/ShapeS";
|
||||
import ShapeT from "./Shapes/ShapeT";
|
||||
import ShapeZ from "./Shapes/ShapeZ";
|
||||
import type {coordinates} from './Shapes/BaseShape';
|
||||
import type {grid} from './GridManager';
|
||||
|
||||
/**
|
||||
* Class used as an abstraction layer for shapes.
|
||||
* Use this class to manipulate pieces rather than Shapes directly
|
||||
*
|
||||
*/
|
||||
export default class Piece {
|
||||
|
||||
#shapes = [
|
||||
|
@ -20,17 +27,32 @@ export default class Piece {
|
|||
#currentShape: Object;
|
||||
#colors: Object;
|
||||
|
||||
/**
|
||||
* Initializes this piece's color and shape
|
||||
*
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
constructor(colors: Object) {
|
||||
this.#currentShape = this.getRandomShape(colors);
|
||||
this.#colors = colors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random shape object
|
||||
*
|
||||
* @param colors Object containing current theme colors
|
||||
*/
|
||||
getRandomShape(colors: Object) {
|
||||
return new this.#shapes[Math.floor(Math.random() * 7)](colors);
|
||||
}
|
||||
|
||||
removeFromGrid(grid) {
|
||||
const coord = this.#currentShape.getCellsCoordinates(true);
|
||||
/**
|
||||
* Removes the piece from the given grid
|
||||
*
|
||||
* @param grid The grid to remove the piece from
|
||||
*/
|
||||
removeFromGrid(grid: grid) {
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
grid[coord[i].y][coord[i].x] = {
|
||||
color: this.#colors.tetrisBackground,
|
||||
|
@ -39,8 +61,14 @@ export default class Piece {
|
|||
}
|
||||
}
|
||||
|
||||
toGrid(grid: Array<Array<Object>>, isPreview: boolean) {
|
||||
const coord = this.#currentShape.getCellsCoordinates(!isPreview);
|
||||
/**
|
||||
* 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: grid, isPreview: boolean) {
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(!isPreview);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
grid[coord[i].y][coord[i].x] = {
|
||||
color: this.#currentShape.getColor(),
|
||||
|
@ -49,9 +77,17 @@ export default class Piece {
|
|||
}
|
||||
}
|
||||
|
||||
isPositionValid(grid, width, height) {
|
||||
/**
|
||||
* 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: grid, width: number, height: number) {
|
||||
let isValid = true;
|
||||
const coord = this.#currentShape.getCellsCoordinates(true);
|
||||
const coord: Array<coordinates> = this.#currentShape.getCellsCoordinates(true);
|
||||
for (let i = 0; i < coord.length; i++) {
|
||||
if (coord[i].x >= width
|
||||
|| coord[i].x < 0
|
||||
|
@ -65,7 +101,18 @@ export default class Piece {
|
|||
return isValid;
|
||||
}
|
||||
|
||||
tryMove(x: number, y: number, grid, width, height, freezeCallback: Function) {
|
||||
/**
|
||||
* 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: grid, width: number, height: number, freezeCallback: Function) {
|
||||
if (x > 1) x = 1; // Prevent moving from more than one tile
|
||||
if (x < -1) x = -1;
|
||||
if (y > 1) y = 1;
|
||||
|
@ -75,19 +122,26 @@ export default class Piece {
|
|||
this.removeFromGrid(grid);
|
||||
this.#currentShape.move(x, y);
|
||||
let isValid = this.isPositionValid(grid, width, height);
|
||||
let shouldFreeze = false;
|
||||
|
||||
if (!isValid)
|
||||
this.#currentShape.move(-x, -y);
|
||||
|
||||
shouldFreeze = !isValid && y !== 0;
|
||||
let shouldFreeze = !isValid && y !== 0;
|
||||
this.toGrid(grid, false);
|
||||
if (shouldFreeze)
|
||||
freezeCallback();
|
||||
return isValid;
|
||||
}
|
||||
|
||||
tryRotate(grid, width, height) {
|
||||
/**
|
||||
* 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: grid, width: number, height: number) {
|
||||
this.removeFromGrid(grid);
|
||||
this.#currentShape.rotate(true);
|
||||
if (!this.isPositionValid(grid, width, height)) {
|
||||
|
@ -99,7 +153,12 @@ export default class Piece {
|
|||
return true;
|
||||
}
|
||||
|
||||
getCoordinates() {
|
||||
/**
|
||||
* Gets this piece used cells coordinates
|
||||
*
|
||||
* @return {Array<coordinates>} An array of coordinates
|
||||
*/
|
||||
getCoordinates(): Array<coordinates> {
|
||||
return this.#currentShape.getCellsCoordinates(true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
// @flow
|
||||
|
||||
/**
|
||||
* Class used to manage game score
|
||||
*/
|
||||
export default class ScoreManager {
|
||||
|
||||
#scoreLinesModifier = [40, 100, 300, 1200];
|
||||
|
@ -8,31 +11,60 @@ export default class ScoreManager {
|
|||
#level: number;
|
||||
#levelProgression: number;
|
||||
|
||||
/**
|
||||
* Initializes score to 0
|
||||
*/
|
||||
constructor() {
|
||||
this.#score = 0;
|
||||
this.#level = 0;
|
||||
this.#levelProgression = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current score
|
||||
*
|
||||
* @return {number} The current score
|
||||
*/
|
||||
getScore(): number {
|
||||
return this.#score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current level
|
||||
*
|
||||
* @return {number} The current level
|
||||
*/
|
||||
getLevel(): number {
|
||||
return this.#level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current level progression
|
||||
*
|
||||
* @return {number} The current level progression
|
||||
*/
|
||||
getLevelProgression(): number {
|
||||
return this.#levelProgression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the score by one
|
||||
*/
|
||||
incrementScore() {
|
||||
this.#score++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add score corresponding to the number of lines removed at the same time.
|
||||
* Also updates the level progression.
|
||||
*
|
||||
* The more lines cleared at the same time, the more points and level progression the player gets.
|
||||
*
|
||||
* @param numberRemoved The number of lines removed at the same time
|
||||
*/
|
||||
addLinesRemovedPoints(numberRemoved: number) {
|
||||
if (numberRemoved < 1 || numberRemoved > 4)
|
||||
return 0;
|
||||
return;
|
||||
this.#score += this.#scoreLinesModifier[numberRemoved-1] * (this.#level + 1);
|
||||
switch (numberRemoved) {
|
||||
case 1:
|
||||
|
@ -50,6 +82,13 @@ export default class ScoreManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the player can go to the next level.
|
||||
*
|
||||
* If he can, change the level.
|
||||
*
|
||||
* @return {boolean} True if the current level has changed
|
||||
*/
|
||||
canLevelUp() {
|
||||
let canLevel = this.#levelProgression > this.#level * 5;
|
||||
if (canLevel){
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
// @flow
|
||||
|
||||
export type coordinates = {
|
||||
x: number,
|
||||
y: number,
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -9,8 +14,11 @@ export default class BaseShape {
|
|||
|
||||
#currentShape: Array<Array<number>>;
|
||||
#rotation: number;
|
||||
position: Object;
|
||||
position: coordinates;
|
||||
|
||||
/**
|
||||
* Prevent instantiation if classname is BaseShape to force class to be abstract
|
||||
*/
|
||||
constructor() {
|
||||
if (this.constructor === BaseShape)
|
||||
throw new Error("Abstract class can't be instantiated");
|
||||
|
@ -19,19 +27,41 @@ export default class BaseShape {
|
|||
this.#currentShape = this.getShapes()[this.#rotation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this shape's color.
|
||||
* Must be implemented by child class
|
||||
*/
|
||||
getColor(): string {
|
||||
throw new Error("Method 'getColor()' must be implemented");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this object's all possible shapes as an array.
|
||||
* Must be implemented by child class.
|
||||
*
|
||||
* Used by tests to read private fields
|
||||
*/
|
||||
getShapes(): Array<Array<Array<number>>> {
|
||||
throw new Error("Method 'getShapes()' must be implemented");
|
||||
}
|
||||
|
||||
getCurrentShape() {
|
||||
/**
|
||||
* Gets this object's current shape.
|
||||
*
|
||||
* Used by tests to read private fields
|
||||
*/
|
||||
getCurrentShape(): Array<Array<number>> {
|
||||
return this.#currentShape;
|
||||
}
|
||||
|
||||
getCellsCoordinates(isAbsolute: boolean) {
|
||||
/**
|
||||
* Gets this object's coordinates.
|
||||
* 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?
|
||||
* @return {Array<coordinates>} This object cells coordinates
|
||||
*/
|
||||
getCellsCoordinates(isAbsolute: boolean): Array<coordinates> {
|
||||
let coordinates = [];
|
||||
for (let row = 0; row < this.#currentShape.length; row++) {
|
||||
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
||||
|
@ -45,6 +75,11 @@ export default class BaseShape {
|
|||
return coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotate this object
|
||||
*
|
||||
* @param isForward Should we rotate clockwise?
|
||||
*/
|
||||
rotate(isForward: boolean) {
|
||||
if (isForward)
|
||||
this.#rotation++;
|
||||
|
@ -57,6 +92,12 @@ export default class BaseShape {
|
|||
this.#currentShape = this.getShapes()[this.#rotation];
|
||||
}
|
||||
|
||||
/**
|
||||
* Move this object
|
||||
*
|
||||
* @param x Position X offset to add
|
||||
* @param y Position Y offset to add
|
||||
*/
|
||||
move(x: number, y: number) {
|
||||
this.position.x += x;
|
||||
this.position.y += y;
|
||||
|
|
Loading…
Reference in a new issue