/* * 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 . */ import Piece from './Piece'; import ScoreManager from './ScoreManager'; import GridManager from './GridManager'; import type { GridType } from '../components/GridComponent'; export type TickCallbackType = ( score: number, level: number, grid: GridType ) => void; export type ClockCallbackType = (time: number) => void; export type EndCallbackType = ( time: number, score: number, isRestart: boolean ) => void; export type MovementCallbackType = (grid: GridType, score?: number) => void; export default class GameLogic { static levelTicks = [1000, 800, 600, 400, 300, 200, 150, 100]; scoreManager: ScoreManager; gridManager: GridManager; height: number; width: number; gameRunning: boolean; gamePaused: boolean; gameTime: number; currentObject?: Piece; gameTick: number; gameTickInterval?: NodeJS.Timeout; gameTimeInterval?: NodeJS.Timeout; pressInInterval?: NodeJS.Timeout; isPressedIn: boolean; autoRepeatActivationDelay: number; autoRepeatDelay: number; nextPieces: Array; nextPiecesCount: number; tickCallback?: TickCallbackType; clockCallback?: ClockCallbackType; endCallback?: EndCallbackType; theme: ReactNativePaper.Theme; constructor(height: number, width: number, theme: ReactNativePaper.Theme) { this.gameTime = 0; this.gameTick = 0; this.isPressedIn = false; this.height = height; this.width = width; this.gameRunning = false; this.gamePaused = false; this.theme = theme; this.autoRepeatActivationDelay = 300; this.autoRepeatDelay = 50; this.nextPieces = []; this.nextPiecesCount = 3; this.scoreManager = new ScoreManager(); this.gridManager = new GridManager( this.getWidth(), this.getHeight(), this.theme ); } getHeight(): number { return this.height; } getWidth(): number { return this.width; } getCurrentGrid(): GridType { return this.gridManager.getCurrentGrid(); } isGamePaused(): boolean { return this.gamePaused; } onFreeze = () => { if (this.currentObject) { this.gridManager.freezeTetromino(this.currentObject, this.scoreManager); } this.createTetromino(); }; setNewGameTick(level: number) { if (level >= GameLogic.levelTicks.length) { return; } this.gameTick = GameLogic.levelTicks[level]; this.stopTick(); this.startTick(); } startClock() { this.gameTimeInterval = setInterval(() => { this.onClock(this.clockCallback); }, 1000); } startTick() { this.gameTickInterval = setInterval(() => { this.onTick(this.tickCallback); }, this.gameTick); } stopClock() { if (this.gameTimeInterval) { clearInterval(this.gameTimeInterval); } } stopTick() { if (this.gameTickInterval) { clearInterval(this.gameTickInterval); } } stopGameTime() { this.stopClock(); this.stopTick(); } startGameTime() { this.startClock(); this.startTick(); } onTick(callback?: TickCallbackType) { if (this.currentObject) { this.currentObject.tryMove( 0, 1, this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight(), this.onFreeze ); } if (callback) { callback( this.scoreManager.getScore(), this.scoreManager.getLevel(), this.gridManager.getCurrentGrid() ); } if (this.scoreManager.canLevelUp()) { this.setNewGameTick(this.scoreManager.getLevel()); } } onClock(callback?: ClockCallbackType) { this.gameTime += 1; if (callback) { callback(this.gameTime); } } canUseInput(): boolean { return this.gameRunning && !this.gamePaused; } rightPressed(callback: MovementCallbackType) { this.isPressedIn = true; this.movePressedRepeat(true, callback, 1, 0); } leftPressedIn(callback: MovementCallbackType) { this.isPressedIn = true; this.movePressedRepeat(true, callback, -1, 0); } downPressedIn(callback: MovementCallbackType) { this.isPressedIn = true; this.movePressedRepeat(true, callback, 0, 1); } movePressedRepeat( isInitial: boolean, callback: MovementCallbackType, x: number, y: number ) { if (!this.canUseInput() || !this.isPressedIn) { return; } const moved = this.currentObject && 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() ); } else { callback(this.gridManager.getCurrentGrid()); } } this.pressInInterval = setTimeout( () => { this.movePressedRepeat(false, callback, x, y); }, isInitial ? this.autoRepeatActivationDelay : this.autoRepeatDelay ); } pressedOut() { this.isPressedIn = false; if (this.pressInInterval) { clearTimeout(this.pressInInterval); } } rotatePressed(callback: MovementCallbackType) { if (!this.canUseInput()) { return; } if ( this.currentObject && this.currentObject.tryRotate( this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight() ) ) { callback(this.gridManager.getCurrentGrid()); } } getNextPiecesPreviews(): Array { const finalArray = []; for (let i = 0; i < this.nextPieces.length; i += 1) { const gridSize = this.nextPieces[i].getCurrentShape().getCurrentShape()[0] .length; finalArray.push(this.gridManager.getEmptyGrid(gridSize, gridSize)); this.nextPieces[i].toGrid(finalArray[i], true); } return finalArray; } recoverNextPiece() { const next = this.nextPieces.shift(); if (next) { this.currentObject = next; } this.generateNextPieces(); } generateNextPieces() { while (this.nextPieces.length < this.nextPiecesCount) { this.nextPieces.push(new Piece(this.theme)); } } createTetromino() { this.pressedOut(); this.recoverNextPiece(); if ( this.currentObject && !this.currentObject.isPositionValid( this.gridManager.getCurrentGrid(), this.getWidth(), this.getHeight() ) ) { this.endGame(false); } } togglePause() { if (!this.gameRunning) { return; } this.gamePaused = !this.gamePaused; if (this.gamePaused) { this.stopGameTime(); } else { this.startGameTime(); } } endGame(isRestart: boolean) { this.gameRunning = false; this.gamePaused = false; this.stopGameTime(); if (this.endCallback) { this.endCallback(this.gameTime, this.scoreManager.getScore(), isRestart); } } startGame( tickCallback: TickCallbackType, clockCallback: ClockCallbackType, endCallback: EndCallbackType ) { 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.theme ); this.nextPieces = []; this.generateNextPieces(); this.createTetromino(); tickCallback( this.scoreManager.getScore(), this.scoreManager.getLevel(), this.gridManager.getCurrentGrid() ); clockCallback(this.gameTime); this.startTick(); this.startClock(); this.tickCallback = tickCallback; this.clockCallback = clockCallback; this.endCallback = endCallback; } }