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,
|
100,
|
||||||
];
|
];
|
||||||
|
|
||||||
scoreManager: ScoreManager;
|
#scoreManager: ScoreManager;
|
||||||
gridManager: GridManager;
|
#gridManager: GridManager;
|
||||||
|
|
||||||
height: number;
|
#height: number;
|
||||||
width: number;
|
#width: number;
|
||||||
|
|
||||||
gameRunning: boolean;
|
#gameRunning: boolean;
|
||||||
gamePaused: boolean;
|
#gamePaused: boolean;
|
||||||
gameTime: number;
|
#gameTime: number;
|
||||||
|
|
||||||
currentObject: Piece;
|
#currentObject: Piece;
|
||||||
|
|
||||||
gameTick: number;
|
#gameTick: number;
|
||||||
gameTickInterval: IntervalID;
|
#gameTickInterval: IntervalID;
|
||||||
gameTimeInterval: IntervalID;
|
#gameTimeInterval: IntervalID;
|
||||||
|
|
||||||
pressInInterval: TimeoutID;
|
#pressInInterval: TimeoutID;
|
||||||
isPressedIn: boolean;
|
#isPressedIn: boolean;
|
||||||
autoRepeatActivationDelay: number;
|
#autoRepeatActivationDelay: number;
|
||||||
autoRepeatDelay: number;
|
#autoRepeatDelay: number;
|
||||||
|
|
||||||
nextPieces: Array<Piece>;
|
#nextPieces: Array<Piece>;
|
||||||
nextPiecesCount: number;
|
#nextPiecesCount: number;
|
||||||
|
|
||||||
onTick: Function;
|
#onTick: Function;
|
||||||
onClock: Function;
|
#onClock: Function;
|
||||||
endCallback: Function;
|
endCallback: Function;
|
||||||
|
|
||||||
colors: Object;
|
#colors: Object;
|
||||||
|
|
||||||
constructor(height: number, width: number, colors: Object) {
|
constructor(height: number, width: number, colors: Object) {
|
||||||
this.height = height;
|
this.#height = height;
|
||||||
this.width = width;
|
this.#width = width;
|
||||||
this.gameRunning = false;
|
this.#gameRunning = false;
|
||||||
this.gamePaused = false;
|
this.#gamePaused = false;
|
||||||
this.colors = colors;
|
this.#colors = colors;
|
||||||
this.autoRepeatActivationDelay = 300;
|
this.#autoRepeatActivationDelay = 300;
|
||||||
this.autoRepeatDelay = 50;
|
this.#autoRepeatDelay = 50;
|
||||||
this.nextPieces = [];
|
this.#nextPieces = [];
|
||||||
this.nextPiecesCount = 3;
|
this.#nextPiecesCount = 3;
|
||||||
this.scoreManager = new ScoreManager();
|
this.#scoreManager = new ScoreManager();
|
||||||
this.gridManager = new GridManager(this.getWidth(), this.getHeight(), this.colors);
|
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHeight(): number {
|
getHeight(): number {
|
||||||
return this.height;
|
return this.#height;
|
||||||
}
|
}
|
||||||
|
|
||||||
getWidth(): number {
|
getWidth(): number {
|
||||||
return this.width;
|
return this.#width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentGrid() {
|
getCurrentGrid() {
|
||||||
return this.gridManager.getCurrentGrid();
|
return this.#gridManager.getCurrentGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
isGameRunning(): boolean {
|
isGameRunning(): boolean {
|
||||||
return this.gameRunning;
|
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];
|
||||||
clearInterval(this.gameTickInterval);
|
clearInterval(this.#gameTickInterval);
|
||||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||||
}
|
}
|
||||||
|
|
||||||
onTick(callback: Function) {
|
onTick(callback: Function) {
|
||||||
this.currentObject.tryMove(0, 1,
|
this.#currentObject.tryMove(0, 1,
|
||||||
this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||||
() => this.onFreeze());
|
() => 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())
|
if (this.#scoreManager.canLevelUp())
|
||||||
this.setNewGameTick(this.scoreManager.getLevel());
|
this.setNewGameTick(this.#scoreManager.getLevel());
|
||||||
}
|
}
|
||||||
|
|
||||||
onClock(callback: Function) {
|
onClock(callback: Function) {
|
||||||
this.gameTime++;
|
this.#gameTime++;
|
||||||
callback(this.gameTime);
|
callback(this.#gameTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
canUseInput() {
|
canUseInput() {
|
||||||
return this.gameRunning && !this.gamePaused
|
return this.#gameRunning && !this.#gamePaused
|
||||||
}
|
}
|
||||||
|
|
||||||
rightPressed(callback: Function) {
|
rightPressed(callback: Function) {
|
||||||
this.isPressedIn = true;
|
this.#isPressedIn = true;
|
||||||
this.movePressedRepeat(true, callback, 1, 0);
|
this.movePressedRepeat(true, callback, 1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
leftPressedIn(callback: Function) {
|
leftPressedIn(callback: Function) {
|
||||||
this.isPressedIn = true;
|
this.#isPressedIn = true;
|
||||||
this.movePressedRepeat(true, callback, -1, 0);
|
this.movePressedRepeat(true, callback, -1, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
downPressedIn(callback: Function) {
|
downPressedIn(callback: Function) {
|
||||||
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(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,
|
const moved = this.#currentObject.tryMove(x, y,
|
||||||
this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(),
|
||||||
() => this.onFreeze());
|
() => 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(this.#gridManager.getCurrentGrid(), this.#scoreManager.getScore());
|
||||||
} else
|
} 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() {
|
pressedOut() {
|
||||||
this.isPressedIn = false;
|
this.#isPressedIn = false;
|
||||||
clearTimeout(this.pressInInterval);
|
clearTimeout(this.#pressInInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
rotatePressed(callback: Function) {
|
rotatePressed(callback: Function) {
|
||||||
if (!this.canUseInput())
|
if (!this.canUseInput())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (this.currentObject.tryRotate(this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
if (this.#currentObject.tryRotate(this.#gridManager.getCurrentGrid(), this.getWidth(), this.getHeight()))
|
||||||
callback(this.gridManager.getCurrentGrid());
|
callback(this.#gridManager.getCurrentGrid());
|
||||||
}
|
}
|
||||||
|
|
||||||
getNextPiecesPreviews() {
|
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.gridManager.getEmptyGrid(4, 4));
|
finalArray.push(this.#gridManager.getEmptyGrid(4, 4));
|
||||||
this.nextPieces[i].toGrid(finalArray[i], true);
|
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.colors));
|
this.#nextPieces.push(new Piece(this.#colors));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
if (this.#gamePaused) {
|
||||||
clearInterval(this.gameTickInterval);
|
clearInterval(this.#gameTickInterval);
|
||||||
clearInterval(this.gameTimeInterval);
|
clearInterval(this.#gameTimeInterval);
|
||||||
} else {
|
} else {
|
||||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||||
this.gameTimeInterval = setInterval(this.onClock, 1000);
|
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
endGame(isRestart: boolean) {
|
endGame(isRestart: boolean) {
|
||||||
this.gameRunning = false;
|
this.#gameRunning = false;
|
||||||
this.gamePaused = false;
|
this.#gamePaused = false;
|
||||||
clearInterval(this.gameTickInterval);
|
clearInterval(this.#gameTickInterval);
|
||||||
clearInterval(this.gameTimeInterval);
|
clearInterval(this.#gameTimeInterval);
|
||||||
this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart);
|
this.endCallback(this.#gameTime, this.#scoreManager.getScore(), isRestart);
|
||||||
}
|
}
|
||||||
|
|
||||||
startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
|
startGame(tickCallback: Function, clockCallback: Function, endCallback: Function) {
|
||||||
if (this.gameRunning)
|
if (this.#gameRunning)
|
||||||
this.endGame(true);
|
this.endGame(true);
|
||||||
this.gameRunning = true;
|
this.#gameRunning = true;
|
||||||
this.gamePaused = false;
|
this.#gamePaused = false;
|
||||||
this.gameTime = 0;
|
this.#gameTime = 0;
|
||||||
this.scoreManager = new ScoreManager();
|
this.#scoreManager = new ScoreManager();
|
||||||
this.gameTick = GameLogic.levelTicks[this.scoreManager.getLevel()];
|
this.#gameTick = GameLogic.levelTicks[this.#scoreManager.getLevel()];
|
||||||
this.gridManager = new GridManager(this.getWidth(), this.getHeight(), this.colors);
|
this.#gridManager = new GridManager(this.getWidth(), this.getHeight(), this.#colors);
|
||||||
this.nextPieces = [];
|
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);
|
clockCallback(this.#gameTime);
|
||||||
this.onTick = this.onTick.bind(this, tickCallback);
|
this.#onTick = this.onTick.bind(this, tickCallback);
|
||||||
this.onClock = this.onClock.bind(this, clockCallback);
|
this.#onClock = this.onClock.bind(this, clockCallback);
|
||||||
this.gameTickInterval = setInterval(this.onTick, this.gameTick);
|
this.#gameTickInterval = setInterval(this.#onTick, this.#gameTick);
|
||||||
this.gameTimeInterval = setInterval(this.onClock, 1000);
|
this.#gameTimeInterval = setInterval(this.#onClock, 1000);
|
||||||
this.endCallback = endCallback;
|
this.endCallback = endCallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,49 @@
|
||||||
|
|
||||||
import Piece from "./Piece";
|
import Piece from "./Piece";
|
||||||
import ScoreManager from "./ScoreManager";
|
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 {
|
export default class GridManager {
|
||||||
|
|
||||||
#currentGrid: grid;
|
#currentGrid: grid;
|
||||||
#colors: Object;
|
#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) {
|
constructor(width: number, height: number, colors: Object) {
|
||||||
this.#colors = colors;
|
this.#colors = colors;
|
||||||
this.#currentGrid = this.getEmptyGrid(height, width);
|
this.#currentGrid = this.getEmptyGrid(height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentGrid() {
|
/**
|
||||||
|
* Get the current grid
|
||||||
|
*
|
||||||
|
* @return {grid} The current grid
|
||||||
|
*/
|
||||||
|
getCurrentGrid(): grid {
|
||||||
return this.#currentGrid;
|
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 = [];
|
let line = [];
|
||||||
for (let col = 0; col < width; col++) {
|
for (let col = 0; col < width; col++) {
|
||||||
line.push({
|
line.push({
|
||||||
|
@ -30,7 +55,14 @@ export default class GridManager {
|
||||||
return line;
|
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 = [];
|
let grid = [];
|
||||||
for (let row = 0; row < height; row++) {
|
for (let row = 0; row < height; row++) {
|
||||||
grid.push(this.getEmptyLine(width));
|
grid.push(this.getEmptyLine(width));
|
||||||
|
@ -38,6 +70,13 @@ export default class GridManager {
|
||||||
return grid;
|
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) {
|
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++) {
|
||||||
|
@ -47,7 +86,14 @@ export default class GridManager {
|
||||||
scoreManager.addLinesRemovedPoints(lines.length);
|
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 = [];
|
let rows = [];
|
||||||
for (let i = 0; i < coord.length; i++) {
|
for (let i = 0; i < coord.length; i++) {
|
||||||
let isLineFull = true;
|
let isLineFull = true;
|
||||||
|
@ -63,6 +109,12 @@ export default class GridManager {
|
||||||
return rows;
|
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) {
|
freezeTetromino(currentObject: Piece, scoreManager: ScoreManager) {
|
||||||
this.clearLines(this.getLinesToClear(currentObject.getCoordinates()), scoreManager);
|
this.clearLines(this.getLinesToClear(currentObject.getCoordinates()), scoreManager);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,14 @@ import ShapeO from "./Shapes/ShapeO";
|
||||||
import ShapeS from "./Shapes/ShapeS";
|
import ShapeS from "./Shapes/ShapeS";
|
||||||
import ShapeT from "./Shapes/ShapeT";
|
import ShapeT from "./Shapes/ShapeT";
|
||||||
import ShapeZ from "./Shapes/ShapeZ";
|
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 {
|
export default class Piece {
|
||||||
|
|
||||||
#shapes = [
|
#shapes = [
|
||||||
|
@ -20,17 +27,32 @@ export default class Piece {
|
||||||
#currentShape: Object;
|
#currentShape: Object;
|
||||||
#colors: Object;
|
#colors: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes this piece's color and shape
|
||||||
|
*
|
||||||
|
* @param colors Object containing current theme colors
|
||||||
|
*/
|
||||||
constructor(colors: Object) {
|
constructor(colors: Object) {
|
||||||
this.#currentShape = this.getRandomShape(colors);
|
this.#currentShape = this.getRandomShape(colors);
|
||||||
this.#colors = colors;
|
this.#colors = colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random shape object
|
||||||
|
*
|
||||||
|
* @param colors Object containing current theme colors
|
||||||
|
*/
|
||||||
getRandomShape(colors: Object) {
|
getRandomShape(colors: Object) {
|
||||||
return new this.#shapes[Math.floor(Math.random() * 7)](colors);
|
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++) {
|
for (let i = 0; i < coord.length; i++) {
|
||||||
grid[coord[i].y][coord[i].x] = {
|
grid[coord[i].y][coord[i].x] = {
|
||||||
color: this.#colors.tetrisBackground,
|
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++) {
|
for (let i = 0; i < coord.length; i++) {
|
||||||
grid[coord[i].y][coord[i].x] = {
|
grid[coord[i].y][coord[i].x] = {
|
||||||
color: this.#currentShape.getColor(),
|
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;
|
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++) {
|
for (let i = 0; i < coord.length; i++) {
|
||||||
if (coord[i].x >= width
|
if (coord[i].x >= width
|
||||||
|| coord[i].x < 0
|
|| coord[i].x < 0
|
||||||
|
@ -65,7 +101,18 @@ export default class Piece {
|
||||||
return isValid;
|
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; // Prevent moving from more than one tile
|
||||||
if (x < -1) x = -1;
|
if (x < -1) x = -1;
|
||||||
if (y > 1) y = 1;
|
if (y > 1) y = 1;
|
||||||
|
@ -75,19 +122,26 @@ export default class Piece {
|
||||||
this.removeFromGrid(grid);
|
this.removeFromGrid(grid);
|
||||||
this.#currentShape.move(x, y);
|
this.#currentShape.move(x, y);
|
||||||
let isValid = this.isPositionValid(grid, width, height);
|
let isValid = this.isPositionValid(grid, width, height);
|
||||||
let shouldFreeze = false;
|
|
||||||
|
|
||||||
if (!isValid)
|
if (!isValid)
|
||||||
this.#currentShape.move(-x, -y);
|
this.#currentShape.move(-x, -y);
|
||||||
|
|
||||||
shouldFreeze = !isValid && y !== 0;
|
let shouldFreeze = !isValid && y !== 0;
|
||||||
this.toGrid(grid, false);
|
this.toGrid(grid, false);
|
||||||
if (shouldFreeze)
|
if (shouldFreeze)
|
||||||
freezeCallback();
|
freezeCallback();
|
||||||
return isValid;
|
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.removeFromGrid(grid);
|
||||||
this.#currentShape.rotate(true);
|
this.#currentShape.rotate(true);
|
||||||
if (!this.isPositionValid(grid, width, height)) {
|
if (!this.isPositionValid(grid, width, height)) {
|
||||||
|
@ -99,7 +153,12 @@ export default class Piece {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCoordinates() {
|
/**
|
||||||
|
* Gets this piece used cells coordinates
|
||||||
|
*
|
||||||
|
* @return {Array<coordinates>} An array of coordinates
|
||||||
|
*/
|
||||||
|
getCoordinates(): Array<coordinates> {
|
||||||
return this.#currentShape.getCellsCoordinates(true);
|
return this.#currentShape.getCellsCoordinates(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class used to manage game score
|
||||||
|
*/
|
||||||
export default class ScoreManager {
|
export default class ScoreManager {
|
||||||
|
|
||||||
#scoreLinesModifier = [40, 100, 300, 1200];
|
#scoreLinesModifier = [40, 100, 300, 1200];
|
||||||
|
@ -8,31 +11,60 @@ export default class ScoreManager {
|
||||||
#level: number;
|
#level: number;
|
||||||
#levelProgression: number;
|
#levelProgression: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes score to 0
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.#score = 0;
|
this.#score = 0;
|
||||||
this.#level = 0;
|
this.#level = 0;
|
||||||
this.#levelProgression = 0;
|
this.#levelProgression = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current score
|
||||||
|
*
|
||||||
|
* @return {number} The current score
|
||||||
|
*/
|
||||||
getScore(): number {
|
getScore(): number {
|
||||||
return this.#score;
|
return this.#score;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current level
|
||||||
|
*
|
||||||
|
* @return {number} The current level
|
||||||
|
*/
|
||||||
getLevel(): number {
|
getLevel(): number {
|
||||||
return this.#level;
|
return this.#level;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current level progression
|
||||||
|
*
|
||||||
|
* @return {number} The current level progression
|
||||||
|
*/
|
||||||
getLevelProgression(): number {
|
getLevelProgression(): number {
|
||||||
return this.#levelProgression;
|
return this.#levelProgression;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increments the score by one
|
||||||
|
*/
|
||||||
incrementScore() {
|
incrementScore() {
|
||||||
this.#score++;
|
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) {
|
addLinesRemovedPoints(numberRemoved: number) {
|
||||||
if (numberRemoved < 1 || numberRemoved > 4)
|
if (numberRemoved < 1 || numberRemoved > 4)
|
||||||
return 0;
|
return;
|
||||||
this.#score += this.#scoreLinesModifier[numberRemoved-1] * (this.#level + 1);
|
this.#score += this.#scoreLinesModifier[numberRemoved-1] * (this.#level + 1);
|
||||||
switch (numberRemoved) {
|
switch (numberRemoved) {
|
||||||
case 1:
|
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() {
|
canLevelUp() {
|
||||||
let canLevel = this.#levelProgression > this.#level * 5;
|
let canLevel = this.#levelProgression > this.#level * 5;
|
||||||
if (canLevel){
|
if (canLevel){
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
|
export type coordinates = {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract class used to represent a BaseShape.
|
* 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
|
* 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>>;
|
#currentShape: Array<Array<number>>;
|
||||||
#rotation: number;
|
#rotation: number;
|
||||||
position: Object;
|
position: coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent instantiation if classname is BaseShape to force class to be abstract
|
||||||
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
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");
|
||||||
|
@ -19,19 +27,41 @@ export default class BaseShape {
|
||||||
this.#currentShape = this.getShapes()[this.#rotation];
|
this.#currentShape = this.getShapes()[this.#rotation];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets this shape's color.
|
||||||
|
* Must be implemented by child class
|
||||||
|
*/
|
||||||
getColor(): string {
|
getColor(): string {
|
||||||
throw new Error("Method 'getColor()' must be implemented");
|
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>>> {
|
getShapes(): Array<Array<Array<number>>> {
|
||||||
throw new Error("Method 'getShapes()' must be implemented");
|
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;
|
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 = [];
|
let coordinates = [];
|
||||||
for (let row = 0; row < this.#currentShape.length; row++) {
|
for (let row = 0; row < this.#currentShape.length; row++) {
|
||||||
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
for (let col = 0; col < this.#currentShape[row].length; col++) {
|
||||||
|
@ -45,6 +75,11 @@ export default class BaseShape {
|
||||||
return coordinates;
|
return coordinates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rotate this object
|
||||||
|
*
|
||||||
|
* @param isForward Should we rotate clockwise?
|
||||||
|
*/
|
||||||
rotate(isForward: boolean) {
|
rotate(isForward: boolean) {
|
||||||
if (isForward)
|
if (isForward)
|
||||||
this.#rotation++;
|
this.#rotation++;
|
||||||
|
@ -57,6 +92,12 @@ export default class BaseShape {
|
||||||
this.#currentShape = this.getShapes()[this.#rotation];
|
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) {
|
move(x: number, y: number) {
|
||||||
this.position.x += x;
|
this.position.x += x;
|
||||||
this.position.y += y;
|
this.position.y += y;
|
||||||
|
|
Loading…
Reference in a new issue