From 4436661547c4e83c57bd62dfb785c5faa4cc907e Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Sat, 18 Jul 2026 17:47:33 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D0=A4=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D1=82=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=D0=B8=D1=80=D0=BF=D0=B8=D1=87=D0=B5=D0=B9=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D0=B5=20Game=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=20=D1=81=20=D0=B2?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BC?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=B8=D0=B2=D0=B0=20=D0=BD=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BC=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D0=B8=D0=B2.=20=D0=92=D1=85=D0=BE=D0=B4=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B7=D0=BD=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=B6=D0=B4=D0=BE=D0=B3=D0=BE=20=D0=BA=D0=B8=D1=80=D0=BF=D0=B8?= =?UTF-8?q?=D1=87=D0=B0=20=D0=B1=D0=B5=D1=80=D1=83=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=BA=D0=B0=D1=80=D1=82=D1=8B=20=D1=83=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/entities/brick/layBricks/layBricks.js | 40 +++++------ .../brick/layBricks/layBricks.test.js | 69 +++++++++++-------- src/game.js | 12 ++-- src/lib/tick/tick.js | 46 ++++++------- src/lib/tick/tick.test.js | 24 ++++--- src/main.js | 3 +- src/view.js | 24 +++---- 7 files changed, 113 insertions(+), 105 deletions(-) diff --git a/src/entities/brick/layBricks/layBricks.js b/src/entities/brick/layBricks/layBricks.js index 9dc722b..1e2858f 100644 --- a/src/entities/brick/layBricks/layBricks.js +++ b/src/entities/brick/layBricks/layBricks.js @@ -1,37 +1,35 @@ import { Brick } from '../brick'; /** - * Ложит кирпичи по заданым размерам - * @param {number} columnAmount количество кирпичей по оси X, неотрицателное, целое число - * @param {number} rowAmount количество кирпичей по оси Y, неотрицателное, целое число + * Создает массив кирпичей проинициализированных значениями в зависимости от расположения на карте уровня + * @param {number[][]} levelMap карта уровня в формате массива * @param {number} brickWidth ширина кирпича, неотрицателное число * @param {number} brickHeight длина кирпича, неотрицателное число - * @returns {Brick[][]} массив с массивами кирпичей + * @returns {Brick[]} массив кирпичей */ -export function layBricks(columnAmount, rowAmount, brickWidth, brickHeight) { +export function layBricks(levelMap, brickWidth, brickHeight) { try { - const someArgsArentNumbers = [columnAmount, rowAmount, brickWidth, brickHeight].some( - (arg) => typeof arg !== 'number', - ); - if (someArgsArentNumbers) { - throw new Error('Параметры кладки кирпичей должны являться числами'); + if (!(Array.isArray(levelMap) && levelMap.every(Array.isArray))) { + throw new Error('Значение карты уровня должно являться вложенным масивом чисел глубины 2'); } - const someArgsLessThanZero = [columnAmount, rowAmount, brickWidth, brickHeight].some((arg) => arg < 0); - if (someArgsLessThanZero) { - throw new Error('Параметры кладки кирпичей должны являться положительными целыми числами'); + if (!(typeof brickWidth === 'number' && typeof brickHeight === 'number')) { + throw new Error('Значения ширины и высоты кирпича должны являться числами'); } - const rowsOrColumnsAreFloat = [columnAmount, rowAmount].some((amount) => amount % 1 !== 0); - if (rowsOrColumnsAreFloat) { - throw new Error('Размеры рядов и колонок должны являться целыми числами'); + if (brickWidth < 0 || brickHeight < 0) { + throw new Error('Значения ширины и высоты кирпича должны являться положительными целыми числами'); } - const bricks = Array.from({ length: rowAmount }).map((_, rowIndex) => - Array.from({ length: columnAmount }).map( - (_, columnIndex) => new Brick(columnIndex * brickWidth, rowIndex * brickHeight, brickWidth, brickHeight), - ), - ); + const bricks = []; + + for (let i = 0; i < levelMap.length; i++) { + for (let j = 0; j < levelMap[i].length; j++) { + if (levelMap[i][j] !== 0) { + bricks.push(new Brick(j * brickWidth, i * brickHeight, brickWidth, brickHeight)); + } + } + } return bricks; } catch (err) { diff --git a/src/entities/brick/layBricks/layBricks.test.js b/src/entities/brick/layBricks/layBricks.test.js index 7e665bb..0a67871 100644 --- a/src/entities/brick/layBricks/layBricks.test.js +++ b/src/entities/brick/layBricks/layBricks.test.js @@ -5,54 +5,63 @@ describe('layBricks', () => { it('Возвращает корректное значение в случае неверного типа параметра', () => { const wrongType = 'wrong'; - expect(layBricks(wrongType, 1, 1, 1)).toBeNull(); - expect(layBricks(1, wrongType, 1, 1)).toBeNull(); - expect(layBricks(1, 1, wrongType, 1)).toBeNull(); - expect(layBricks(1, 1, 1, wrongType)).toBeNull(); + expect(layBricks(wrongType, 1, 1)).toBeNull(); + expect(layBricks(1, wrongType, 1)).toBeNull(); + expect(layBricks(1, 1, wrongType)).toBeNull(); }); it('Возвращает корректное значение в случае неверных размеров кирпича', () => { const wrongWidth = -1; const wrongHeight = -1; - expect(layBricks(1, 1, wrongWidth, 1)).toBeNull(); - expect(layBricks(1, 1, 1, wrongHeight)).toBeNull(); + expect(layBricks(1, wrongWidth, 1)).toBeNull(); + expect(layBricks(1, 1, wrongHeight)).toBeNull(); }); - it('Возвращает корректное значение в случае неверных значений рядов и колонок', () => { - const negativeRow = -1; - const negativeColumn = -1; - const decimalRow = 1.5; - const decimalColumn = 1.5; + it('Возвращает корректное значение в случае некоректной карты уровня', () => { + const levelMap = ['brick', 'brick']; - expect(layBricks(negativeRow, 1, 1, 1)).toBeNull(); - expect(layBricks(1, negativeColumn, 1, 1)).toBeNull(); - expect(layBricks(decimalRow, 1, 1, 1)).toBeNull(); - expect(layBricks(1, decimalColumn, 1, 1)).toBeNull(); + expect(layBricks(levelMap, 1, 1)).toBeNull(); }); it('Возвращает массив корректных размеров', () => { - const columnAmount = 10; - const rowAmount = 5; + const levelMap = [ + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + [1, 1, 1, 1, 1], + ]; - const bricks = layBricks(columnAmount, rowAmount, 1, 1); - expect(bricks.length).toEqual(rowAmount); - for (const row of bricks) { - expect(row.length).toEqual(columnAmount); - } + const bricks = layBricks(levelMap, 1, 1); + expect(bricks.length).toBe(levelMap.flat().length); + + const emptyLevelMap = [ + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + [0, 0, 0, 0, 0], + ]; + const emptyBricksArray = layBricks(emptyLevelMap, 1, 1); + expect(emptyBricksArray.length).toBe(0); }); it('Задает корректные координаты кирпичам', () => { - const bricks = layBricks(3, 3, 1, 1); - const columnAmount = 10; - const rowAmount = 5; + const levelMap = [ + [1, 1], + [1, 1], + ]; + const brickWidth = 1; + const brickHeight = 1; - const brickss = layBricks(columnAmount, rowAmount, 1, 1); + const bricks = layBricks(levelMap, brickWidth, brickHeight); + let index = 0; - for (let i = 0; i < rowAmount; i++) { - for (let j = 0; j < columnAmount; j++) { - expect(brickss[i][j].x).toEqual(j); - expect(brickss[i][j]?.y).toEqual(i); + for (let i = 0; i < levelMap.length; i++) { + for (let j = 0; j < levelMap[i].length; j++) { + if (levelMap[i][j] === 0) { + continue; + } + expect(bricks[index].x).toBe(j * brickWidth); + expect(bricks[index].y).toBe(i * brickHeight); + index++; } } }); diff --git a/src/game.js b/src/game.js index c0c6fc7..71df444 100644 --- a/src/game.js +++ b/src/game.js @@ -20,12 +20,13 @@ import { toRadians } from './lib/toRadians/toRadians'; */ export class Game { /** - * @param {number} columnAmount количество кирпичей по оси X, неотрицателное, целое число - * @param {number} rowAmount количество кирпичей по оси Y, неотрицателное, целое число + * @param {number[][][]} levels список уровней, каждый элемент которого - карта расположения блоков */ - constructor(columnAmount, rowAmount) { + constructor(levels) { this.livesAmount = 3; this.status = 'in_process'; + this.currentLevel = 0; + this.maxLevel = levels.length - 1; this.ball = new Ball( CONTAINER_WIDTH / 2 - BALL_RADIUS / 2, @@ -35,8 +36,7 @@ export class Game { -1 * BALL_SPEED * Math.sin(toRadians(BALL_INITIAL_ANGLE)), ); this.paddle = new Paddle(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT); - - this.bricks = layBricks(columnAmount, rowAmount, BRICK_WIDTH, BRICK_HEIGHT); + this.bricks = layBricks(levels[this.currentLevel], BRICK_WIDTH, BRICK_HEIGHT); } /** @@ -59,7 +59,7 @@ export class Game { } } - const isAnyBrickAlive = this.bricks.some((row) => row.some((brick) => brick.alive)); + const isAnyBrickAlive = this.bricks.some((brick) => brick.alive); if (!isAnyBrickAlive) { this.status = 'completed'; diff --git a/src/lib/tick/tick.js b/src/lib/tick/tick.js index bab86bc..21cd7d2 100644 --- a/src/lib/tick/tick.js +++ b/src/lib/tick/tick.js @@ -66,36 +66,34 @@ export function tick(game, containerWidth, containerHeight, deltaTime) { } // Базоввое взаимодействие мяча и кирпича - for (const row of bricks) { - for (const brick of row) { - if (!brick.alive) { - continue; - } + for (const brick of bricks) { + if (!brick.alive) { + continue; + } - const ballLeft = ball.x - ball.radius; - const ballRight = ball.x + ball.radius; - const ballTop = ball.y - ball.radius; - const ballBottom = ball.y + ball.radius; + const ballLeft = ball.x - ball.radius; + const ballRight = ball.x + ball.radius; + const ballTop = ball.y - ball.radius; + const ballBottom = ball.y + ball.radius; - const brickLeft = brick.x; - const brickRight = brick.x + brick.width; - const brickTop = brick.y; - const brickBottom = brick.y + brick.height; + const brickLeft = brick.x; + const brickRight = brick.x + brick.width; + const brickTop = brick.y; + const brickBottom = brick.y + brick.height; - const ballObject = { left: ballLeft, right: ballRight, top: ballTop, bottom: ballBottom }; - const brickObject = { left: brickLeft, right: brickRight, top: brickTop, bottom: brickBottom }; + const ballObject = { left: ballLeft, right: ballRight, top: ballTop, bottom: ballBottom }; + const brickObject = { left: brickLeft, right: brickRight, top: brickTop, bottom: brickBottom }; - const isCollided = calculateCollision(ballObject, brickObject); + const isCollided = calculateCollision(ballObject, brickObject); - if (isCollided) { - const directions = calculateDirection(ballObject, brickObject); + if (isCollided) { + const directions = calculateDirection(ballObject, brickObject); - if (directions !== null) { - ball.horizontalSpeed *= directions[0]; - ball.verticalSpeed *= directions[1]; - brick.kill(); - return; - } + if (directions !== null) { + ball.horizontalSpeed *= directions[0]; + ball.verticalSpeed *= directions[1]; + brick.kill(); + return; } } } diff --git a/src/lib/tick/tick.test.js b/src/lib/tick/tick.test.js index 6e3b181..3c3a047 100644 --- a/src/lib/tick/tick.test.js +++ b/src/lib/tick/tick.test.js @@ -11,7 +11,8 @@ describe('tick', () => { }); it('Возвращает корректное значние и не изменяет свойства game при неверых размерах контейнера', () => { - const game = new Game(1, 1); + const levels = [[[1]]]; + const game = new Game(levels); const ballX = game.ball.x; const ballY = game.ball.y; expect(tick(game, -1, -1, 1)).toBeNull(); @@ -20,7 +21,8 @@ describe('tick', () => { }); it('Возвращает корректное значние и не изменяет свойства game при неверном значении времени', () => { - const game = new Game(1, 1); + const levels = [[[1]]]; + const game = new Game(levels); const ballX = game.ball.x; const ballY = game.ball.y; expect(tick(game, 1, 1, -1)).toBeNull(); @@ -29,7 +31,8 @@ describe('tick', () => { }); it('Меняет направление мяча при столкновении со стеной', () => { - const game = new Game(1, 1); + const levels = [[[1]]]; + const game = new Game(levels); game.ball.x = 0; game.ball.horizontalSpeed = -10; tick(game, CONTAINER_WIDTH, CONTAINER_HEIGHT, 1); @@ -38,7 +41,8 @@ describe('tick', () => { }); it('Меняет направление мяча при столкновении с потолком', () => { - const game = new Game(0, 0); + const levels = [[[1]]]; + const game = new Game(1); game.ball.y = 0; game.ball.verticalSpeed = -10; tick(game, CONTAINER_WIDTH, CONTAINER_HEIGHT, 1); @@ -47,8 +51,9 @@ describe('tick', () => { }); it('Убивает кирпич при столкновении и отражает мяч', () => { - const game = new Game(1, 1); - const brick = game.bricks[0][0]; + const levels = [[[1]]]; + const game = new Game(levels); + const brick = game.bricks[0]; game.ball.x = brick.x + game.ball.radius + 1; game.ball.y = brick.y + brick.height + game.ball.radius + 1; @@ -62,8 +67,9 @@ describe('tick', () => { }); it('Убивает только один кирпич за тик', () => { - const game = new Game(2, 1); - const [firstBrick, secondBrick] = game.bricks[0]; + const levels = [[[1, 1]]]; + const game = new Game(levels); + const [firstBrick, secondBrick] = game.bricks; game.ball.x = firstBrick.x + game.ball.radius + 1; game.ball.y = firstBrick.y + firstBrick.height + game.ball.radius + 1; @@ -72,7 +78,7 @@ describe('tick', () => { tick(game, CONTAINER_WIDTH, CONTAINER_HEIGHT, 0.1); - const killedCount = game.bricks[0].filter((b) => !b.alive).length; + const killedCount = game.bricks.filter((b) => !b.alive).length; expect(killedCount).toBe(1); }); }); diff --git a/src/main.js b/src/main.js index ed9fa34..2631997 100644 --- a/src/main.js +++ b/src/main.js @@ -13,6 +13,7 @@ import { PADDLE_HEIGHT, PADDLE_WIDTH, } from './config'; +import { LEVELS } from './const/levels'; import { Game } from './game'; import { calculateCollision } from './lib/calculateCollision/calculateCollision'; import { calculateDirection } from './lib/calculateDirection/calculateDirection'; @@ -39,7 +40,7 @@ import { createGameView, syncronizeViewsWithGame } from './view'; app.stage.addChild(container); - const game = new Game(BRICK_COLUMN_AMOUNT, BRICK_ROW_AMOUNT); + const game = new Game(LEVELS); const views = createGameView(game, container); container.on('pointermove', (event) => { diff --git a/src/view.js b/src/view.js index 4aaec1b..99b76af 100644 --- a/src/view.js +++ b/src/view.js @@ -41,13 +41,11 @@ export function createGameView(game, container) { const paddle = createPaddleView(game.paddle); container.addChild(paddle); - const bricks = game.bricks.map((row) => - row.map((brick) => { - const brickView = createBrickView(brick); - container.addChild(brickView); - return brickView; - }), - ); + const bricks = game.bricks.map((brick) => { + const brickView = createBrickView(brick); + container.addChild(brickView); + return brickView; + }); return { ball, @@ -78,13 +76,11 @@ export function syncronizeViewsWithGame(views, game) { views.paddle.y = game.paddle.y; for (let i = 0; i < game.bricks.length; i++) { - for (let j = 0; j < game.bricks[i].length; j++) { - const brick = game.bricks[i][j]; - const brickView = views.bricks[i][j]; - brickView.x = brick.x; - brickView.y = brick.y; - brickView.visible = brick.alive; - } + const brick = game.bricks[i]; + const brickView = views.bricks[i]; + brickView.x = brick.x; + brickView.y = brick.y; + brickView.visible = brick.alive; } } catch (err) { console.error(err);