import { BALL_INITIAL_ANGLE, BALL_RADIUS, BALL_SPEED, BALL_SPEED_LEVEL_STEP, BALL_SPEED_TIME_STEP, BRICK_HEIGHT, BRICK_WIDTH, CONTAINER_HEIGHT, CONTAINER_WIDTH, HEADER_HEIGHT, PADDLE_HEIGHT, PADDLE_WIDTH, PERK_BALL_SPEED_DECREASE, } from './config'; import { Ball } from './entities/ball/ball'; import { layBricks } from './entities/brick/layBricks/layBricks'; import { Paddle } from './entities/paddle/paddle'; import { tick } from './lib/tick/tick'; /** * Класс игры c информацией о всех игровых сущностях */ export class Game { /** * @param {number[][][]} levels список уровней, каждый элемент которого - карта расположения блоков */ constructor(levels) { this.livesAmount = 3; this.status = 'in_process'; this.levels = levels; this.currentLevel = 0; this.maxLevel = levels.length - 1; this.paddle = new Paddle(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT); this.balls = [new Ball(0, 0, BALL_RADIUS, BALL_SPEED, BALL_INITIAL_ANGLE)]; this.bricks = layBricks(this.levels[this.currentLevel], BRICK_WIDTH, BRICK_HEIGHT, HEADER_HEIGHT); this.perks = []; this._placeBallOnPaddle(); } /** * Основной (первый) мяч. Пока играет один мяч - это он. * @returns {Ball} */ get ball() { return this.balls[0]; } /** * Изменяет значения сущностей в зависимости от времени * @param {*} deltaTime изменение времени из Ticker */ update(deltaTime) { if (this.status !== 'in_process') { return; } // Пока мяч не запущен - держим его на ракетке и не считаем физику if (this.ball.status === 'idle') { this._placeBallOnPaddle(); return; } tick(this, CONTAINER_WIDTH, CONTAINER_HEIGHT, deltaTime); // Убираем улетевшие мячи. Жизнь теряется только когда не осталось ни одного const survivingBalls = this.balls.filter((ball) => ball.status !== 'out'); if (survivingBalls.length === 0) { this.livesAmount -= 1; if (this.livesAmount === 0) { this.status = 'over'; } else { this._placeBallOnPaddle(); } return; } this.balls = survivingBalls; for (const ball of this.balls) { ball.increaseSpeed(BALL_SPEED_TIME_STEP * deltaTime); } const isLevelComplete = this._checkLevelCompletion(); if (isLevelComplete) { this.currentLevel += 1; if (this.currentLevel <= this.maxLevel) { this._proceedToNextLevel(); } else { this.status = 'completed'; } } } /** * Проверяет завершен ли текущий уровень * @returns {boolean} */ _checkLevelCompletion() { return this.bricks.every((brick) => brick.type === 3 || !brick.alive); } /** * Запускает переход на новый уровень, возвращает мяч в дефоотное положение и отрисовывает кирпичи по карте уровня. */ _proceedToNextLevel() { this.ball.increaseSpeed(BALL_SPEED_LEVEL_STEP); this._placeBallOnPaddle(); this.bricks = layBricks(this.levels[this.currentLevel], BRICK_WIDTH, BRICK_HEIGHT, HEADER_HEIGHT); } /** * Ставит мяч по центру ракетки, оставляя один мяч в игре */ _placeBallOnPaddle() { this.balls = [this.ball]; this.ball.reset(this.paddle.x + this.paddle.width / 2, this.paddle.y - this.ball.radius); } }