From 74fedc787bb5bcf0e436c2fc75efd3ec030394fd Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 08:30:14 +0300 Subject: [PATCH 1/9] =?UTF-8?q?feat:=20=D0=91=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B0=D0=BA?= =?UTF-8?q?=D0=B5=D1=82=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D0=B4=D0=B2=D0=B8?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=D0=B8=D1=8E=20=D0=BA=D1=83=D1=80=D1=81=D0=BE?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.js | 5 +++++ src/main.js | 42 +++++++++++++++++------------------------- 2 files changed, 22 insertions(+), 25 deletions(-) create mode 100644 src/config.js diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..7be99b5 --- /dev/null +++ b/src/config.js @@ -0,0 +1,5 @@ +export const CONTAINER_WIDTH = 800; +export const CONTAINER_HEIGHT = 600; + +export const PADDLE_WIDTH = 50; +export const PADDLE_HEIGHT = 10; diff --git a/src/main.js b/src/main.js index 20b064f..bd22d91 100644 --- a/src/main.js +++ b/src/main.js @@ -1,45 +1,37 @@ import './style.css'; -import { Application, Assets, Container, Sprite } from 'pixi.js'; +import { Application, Assets, Container, Graphics, Sprite } from 'pixi.js'; +import { CONTAINER_HEIGHT, CONTAINER_WIDTH, PADDLE_HEIGHT, PADDLE_WIDTH } from './config'; (async () => { // Create a new application const app = new Application(); // Initialize the application - await app.init({ background: '#1099bb', resizeTo: window }); + await app.init({ background: '#1099bb', width: CONTAINER_WIDTH, height: CONTAINER_HEIGHT }); // Append the application canvas to the document body document.body.appendChild(app.canvas); // Create and add a container to the stage - const container = new Container(); + const container = new Container({ + eventMode: 'static', + hitArea: app.screen, + }); + + container.x = 0; + container.y = 0; app.stage.addChild(container); - // Load the bunny texture - const texture = await Assets.load('https://pixijs.com/assets/bunny.png'); + const paddle = new Graphics().rect(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT).fill('#fff000'); - // Create a 5x5 grid of bunnies in the container - for (let i = 0; i < 25; i++) { - const bunny = new Sprite(texture); + container.addChild(paddle); - bunny.x = (i % 5) * 40; - bunny.y = Math.floor(i / 5) * 40; - container.addChild(bunny); - } + container.on('pointermove', (event) => { + const localPosition = container.toLocal(event.global); - // Move the container to the center - container.x = app.screen.width / 2; - container.y = app.screen.height / 2; - - // Center the bunny sprites in local container coordinates - container.pivot.x = container.width / 2; - container.pivot.y = container.height / 2; - - // Listen for animate update - app.ticker.add((time) => { - // Continuously rotate the container! - // * use delta to create frame-independent transform * - container.rotation -= 0.01 * time.deltaTime; + if (localPosition.x < CONTAINER_WIDTH - PADDLE_WIDTH) { + paddle.x = localPosition.x; + } }); })(); -- 2.54.0 From a43d5c5ae2e5d784fea06f691fae27a2de1f79b1 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 09:05:55 +0300 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20=D0=91=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D0=B4?= =?UTF-8?q?=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BC=D1=8F=D1=87?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D1=81=D1=82=D0=BE=D0=BB=D0=BA=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BE=20=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.js | 4 ++++ src/main.js | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/config.js b/src/config.js index 7be99b5..49deba3 100644 --- a/src/config.js +++ b/src/config.js @@ -3,3 +3,7 @@ export const CONTAINER_HEIGHT = 600; export const PADDLE_WIDTH = 50; export const PADDLE_HEIGHT = 10; + +export const BALL_RADIUS = 10; +export const BALL_SPEED = 3; +export const BALL_INITIAL_ANGLE = 180; diff --git a/src/main.js b/src/main.js index bd22d91..85012d1 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,14 @@ import './style.css'; import { Application, Assets, Container, Graphics, Sprite } from 'pixi.js'; -import { CONTAINER_HEIGHT, CONTAINER_WIDTH, PADDLE_HEIGHT, PADDLE_WIDTH } from './config'; +import { + BALL_INITIAL_ANGLE, + BALL_RADIUS, + BALL_SPEED, + CONTAINER_HEIGHT, + CONTAINER_WIDTH, + PADDLE_HEIGHT, + PADDLE_WIDTH, +} from './config'; (async () => { // Create a new application @@ -24,7 +32,6 @@ import { CONTAINER_HEIGHT, CONTAINER_WIDTH, PADDLE_HEIGHT, PADDLE_WIDTH } from ' app.stage.addChild(container); const paddle = new Graphics().rect(0, CONTAINER_HEIGHT - PADDLE_HEIGHT, PADDLE_WIDTH, PADDLE_HEIGHT).fill('#fff000'); - container.addChild(paddle); container.on('pointermove', (event) => { @@ -34,4 +41,32 @@ import { CONTAINER_HEIGHT, CONTAINER_WIDTH, PADDLE_HEIGHT, PADDLE_WIDTH } from ' paddle.x = localPosition.x; } }); + + const ball = new Graphics().circle(0, 0, BALL_RADIUS).fill('#ffffff'); + container.addChild(ball); + + const leftBoundary = BALL_RADIUS; + const rightBoundary = CONTAINER_WIDTH - BALL_RADIUS; + const topBoundary = BALL_RADIUS; + const bottomBoundary = CONTAINER_HEIGHT - BALL_RADIUS; + + let horizontalSpeed = BALL_SPEED * Math.cos(BALL_INITIAL_ANGLE); + let verticalSpeed = -1 * BALL_SPEED * Math.sin(BALL_INITIAL_ANGLE); + + app.ticker.add((time) => { + // Не даем выйти за границы стен слева / справав и меняем направление + if (ball.x <= leftBoundary || ball.x >= rightBoundary) { + ball.x = ball.x <= leftBoundary ? leftBoundary : rightBoundary; + horizontalSpeed *= -1; + } + + // Не даем выйти за границы стен сверху / снизу и меняем направление + if (ball.y <= topBoundary || ball.y >= bottomBoundary) { + ball.y = ball.y <= topBoundary ? topBoundary : bottomBoundary; + verticalSpeed *= -1; + } + + ball.x += horizontalSpeed * time.deltaTime; + ball.y += verticalSpeed * time.deltaTime; + }); })(); -- 2.54.0 From ccff02d29b852f2288abc098658d21ab52ee52ea Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 09:41:10 +0300 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9?= =?UTF-8?q?=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20=D0=BC=D1=8F=D1=87=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D1=80=D0=B0=D0=BA=D0=B5=D1=82=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main.js b/src/main.js index 85012d1..c730d16 100644 --- a/src/main.js +++ b/src/main.js @@ -49,6 +49,7 @@ import { const rightBoundary = CONTAINER_WIDTH - BALL_RADIUS; const topBoundary = BALL_RADIUS; const bottomBoundary = CONTAINER_HEIGHT - BALL_RADIUS; + const paddleTop = CONTAINER_HEIGHT - PADDLE_HEIGHT; let horizontalSpeed = BALL_SPEED * Math.cos(BALL_INITIAL_ANGLE); let verticalSpeed = -1 * BALL_SPEED * Math.sin(BALL_INITIAL_ANGLE); @@ -66,6 +67,16 @@ import { verticalSpeed *= -1; } + // Базоввое взаимодействие мяча и ракетки + if ( + verticalSpeed > 0 && + ball.y + BALL_RADIUS >= paddleTop && + ball.x >= paddle.x && + ball.x <= paddle.x + PADDLE_WIDTH + ) { + verticalSpeed *= -1; + } + ball.x += horizontalSpeed * time.deltaTime; ball.y += verticalSpeed * time.deltaTime; }); -- 2.54.0 From 4627fe4f7648ba7851d6c45583f0ea34a98524f1 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 12:37:56 +0300 Subject: [PATCH 4/9] =?UTF-8?q?feat(calculateCollision):=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8F=20=D0=B2=D1=8B=D1=87=D0=B8=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D0=B5=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B2=D1=83=D1=85=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=8A=D0=B5=D0=BA=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calculateCollision/calculateCollision.js | 51 +++++++ .../calculateCollision.test.js | 130 ++++++++++++++++++ 2 files changed, 181 insertions(+) create mode 100644 src/lib/calculateCollision/calculateCollision.js create mode 100644 src/lib/calculateCollision/calculateCollision.test.js diff --git a/src/lib/calculateCollision/calculateCollision.js b/src/lib/calculateCollision/calculateCollision.js new file mode 100644 index 0000000..1ed41b9 --- /dev/null +++ b/src/lib/calculateCollision/calculateCollision.js @@ -0,0 +1,51 @@ +/** + * Вычисляет пересеклись ли два объекта по ААBB формуле в системе координат где ось X идет справа налево, ось Y идет сверху вних + * @param {Object} firstObject - первый объект + * @param {number} firstObject.left - Min X координата первого объекта + * @param {number} firstObject.right - Max X координата первого объекта + * @param {number} firstObject.top - Min Y координата первого объекта (ось смотрит вниз) + * @param {number} firstObject.bottom - Max Y координата первого объекта + * @param {Object} secondObject - второй объект + * @param {number} secondObject.left - Min X координата второго объекта + * @param {number} secondObject.right - Max X координата второго объекта + * @param {number} secondObject.top - Min Y координата второго объекта (ось смотрит вниз) + * @param {number} secondObject.bottom - Max Y координата второго объекта + * @returns {boolean} + */ +export function calculateCollision(firstObject, secondObject) { + try { + const coordinates = [ + firstObject.left, + firstObject.right, + firstObject.top, + firstObject.bottom, + secondObject.left, + secondObject.right, + secondObject.top, + secondObject.bottom, + ]; + + if (coordinates.some((element) => typeof element !== 'number')) { + throw new Error('Координаты должны являться числовыми значениями'); + } + + if ( + firstObject.left > firstObject.right || + firstObject.top > firstObject.bottom || + secondObject.left > secondObject.right || + secondObject.top > secondObject.bottom + ) { + throw new Error('Координаты должны быть корректными'); + } + + return ( + firstObject.left <= secondObject.right && + firstObject.right >= secondObject.left && + firstObject.top <= secondObject.bottom && + firstObject.bottom >= secondObject.top + ); + } catch (err) { + console.error(err); + return null; + } +} diff --git a/src/lib/calculateCollision/calculateCollision.test.js b/src/lib/calculateCollision/calculateCollision.test.js new file mode 100644 index 0000000..e6d5748 --- /dev/null +++ b/src/lib/calculateCollision/calculateCollision.test.js @@ -0,0 +1,130 @@ +import { describe, expect, it } from 'vitest'; +import { calculateCollision } from './calculateCollision'; + +describe('calculateCollision', () => { + it('Корректно обрабатывает невозможные кейсы (левая координата больше правой)', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 10, + right: 0, + top: 0, + bottom: 10, + }; + + expect(calculateCollision(firstObject, secondObject)).toBeNull(); + }); + + it('Корректно обрабатывает неверный формат данных', () => { + const firstObject = { + left: 'wrong', + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 0, + right: 10, + top: 0, + bottom: 10, + }; + + expect(calculateCollision(firstObject, secondObject)).toBeNull(); + }); + + it('Корректно обрабатывает отсутствие пересечения по X', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 25, + right: 30, + top: 0, + bottom: 10, + }; + + expect(calculateCollision(firstObject, secondObject)).toBe(false); + }); + + it('Корректно обрабатывает отсутствие пересечения по Y', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 0, + right: 30, + top: 25, + bottom: 30, + }; + + expect(calculateCollision(firstObject, secondObject)).toBe(false); + }); + + it('Корректно обрабатывает отсутствие пересечения по X и Y', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 25, + right: 30, + top: 25, + bottom: 30, + }; + + expect(calculateCollision(firstObject, secondObject)).toBe(false); + }); + + it('Корректно обрабатывает пересечение', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 10, + right: 30, + top: 10, + bottom: 30, + }; + + expect(calculateCollision(firstObject, secondObject)).toBe(true); + }); + + it('Корректно обрабатывает вхождение', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 10, + right: 15, + top: 10, + bottom: 15, + }; + + expect(calculateCollision(firstObject, secondObject)).toBe(true); + }); +}); -- 2.54.0 From d79fb5435bfd1ad7a9741725e49a7b0139eea157 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 12:46:09 +0300 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=20=D0=B1=D0=B0=D0=B7=D0=BE=D0=B2=D1=8B=D0=B9?= =?UTF-8?q?=20=D1=80=D1=8F=D0=B4=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.js | 3 +++ src/main.js | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/config.js b/src/config.js index 49deba3..6784004 100644 --- a/src/config.js +++ b/src/config.js @@ -7,3 +7,6 @@ export const PADDLE_HEIGHT = 10; export const BALL_RADIUS = 10; export const BALL_SPEED = 3; export const BALL_INITIAL_ANGLE = 180; + +export const BRICK_WIDTH = 40; +export const BRICK_HEIGHT = 10; diff --git a/src/main.js b/src/main.js index c730d16..1685f14 100644 --- a/src/main.js +++ b/src/main.js @@ -4,6 +4,8 @@ import { BALL_INITIAL_ANGLE, BALL_RADIUS, BALL_SPEED, + BRICK_HEIGHT, + BRICK_WIDTH, CONTAINER_HEIGHT, CONTAINER_WIDTH, PADDLE_HEIGHT, @@ -42,6 +44,14 @@ import { } }); + const bricksRow = Array.from({ length: Math.floor(CONTAINER_WIDTH / BRICK_WIDTH) }).map((_, index) => { + const brick = new Graphics().rect(0, 0, BRICK_WIDTH, BRICK_HEIGHT).fill('#000fff'); + brick.x = index * BRICK_WIDTH; + brick.y = 1; + container.addChild(brick); + return brick; + }); + const ball = new Graphics().circle(0, 0, BALL_RADIUS).fill('#ffffff'); container.addChild(ball); -- 2.54.0 From 22a0981f1ed1b9667d3384e5d4cb0c96470a228a Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 12:54:33 +0300 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20=D0=91=D0=B0=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B5=20=D0=BC=D1=8F=D1=87=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=B8=D1=80=D0=BF=D0=B8=D1=87=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main.js b/src/main.js index 1685f14..e0a7b27 100644 --- a/src/main.js +++ b/src/main.js @@ -11,6 +11,7 @@ import { PADDLE_HEIGHT, PADDLE_WIDTH, } from './config'; +import { calculateCollision } from './lib/calculateCollision/calculateCollision'; (async () => { // Create a new application @@ -60,18 +61,42 @@ import { const topBoundary = BALL_RADIUS; const bottomBoundary = CONTAINER_HEIGHT - BALL_RADIUS; const paddleTop = CONTAINER_HEIGHT - PADDLE_HEIGHT; + const bricksBottom = BRICK_HEIGHT; let horizontalSpeed = BALL_SPEED * Math.cos(BALL_INITIAL_ANGLE); let verticalSpeed = -1 * BALL_SPEED * Math.sin(BALL_INITIAL_ANGLE); app.ticker.add((time) => { - // Не даем выйти за границы стен слева / справав и меняем направление + // Базоввое взаимодействие мяча и кирпича + for (let i = bricksRow.length - 1; i >= 0; i--) { + const currentBrick = bricksRow[i]; + const brickLeft = currentBrick.x; + const brickRight = currentBrick.x + BRICK_WIDTH; + const brickTop = currentBrick.y; + const brickBottom = currentBrick.y + BRICK_HEIGHT; + + 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 isCollided = calculateCollision( + { left: ballLeft, right: ballRight, top: ballTop, bottom: ballBottom }, + { left: brickLeft, right: brickRight, top: brickTop, bottom: brickBottom }, + ); + + if (isCollided) { + verticalSpeed *= -1; + } + } + + // Не даем мячу выйти за границы стен слева / справав и меняем направление if (ball.x <= leftBoundary || ball.x >= rightBoundary) { ball.x = ball.x <= leftBoundary ? leftBoundary : rightBoundary; horizontalSpeed *= -1; } - // Не даем выйти за границы стен сверху / снизу и меняем направление + // Не даем мячу выйти за границы стен сверху / снизу и меняем направление if (ball.y <= topBoundary || ball.y >= bottomBoundary) { ball.y = ball.y <= topBoundary ? topBoundary : bottomBoundary; verticalSpeed *= -1; -- 2.54.0 From 0a7638bb56a387deddcab1b6193ff85883c0889d Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 13:36:18 +0300 Subject: [PATCH 7/9] =?UTF-8?q?test:=20=D0=9D=D0=B0=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=B9=D0=BA=D0=B0=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20Vitest=20=D0=B1?= =?UTF-8?q?=D0=B5=D0=B7=20=D0=B8=D0=BC=D0=BF=D0=BE=D1=80=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/calculateCollision/calculateCollision.test.js | 1 - vitest.config.js | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/calculateCollision/calculateCollision.test.js b/src/lib/calculateCollision/calculateCollision.test.js index e6d5748..c2581cf 100644 --- a/src/lib/calculateCollision/calculateCollision.test.js +++ b/src/lib/calculateCollision/calculateCollision.test.js @@ -1,4 +1,3 @@ -import { describe, expect, it } from 'vitest'; import { calculateCollision } from './calculateCollision'; describe('calculateCollision', () => { diff --git a/vitest.config.js b/vitest.config.js index d7d6032..f63332c 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -4,5 +4,6 @@ export default defineConfig({ test: { environment: 'jsdom', include: ['src/**/*.test.js'], + globals: true, }, }); -- 2.54.0 From fce9c0ffa34ed2c5f903dc2f028f9e39fc14dc99 Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 14:01:26 +0300 Subject: [PATCH 8/9] =?UTF-8?q?feat(calculateDirection):=20=D0=94=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D0=B2=D1=8B?= =?UTF-8?q?=D1=87=D0=B8=D1=81=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BC=D0=BD?= =?UTF-8?q?=D0=BE=D0=B6=D0=B8=D1=82=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4=D0=B2?= =?UTF-8?q?=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../calculateDirection/calculateDirection.js | 52 ++++++++ .../calculateDirection.test.js | 111 ++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/lib/calculateDirection/calculateDirection.js create mode 100644 src/lib/calculateDirection/calculateDirection.test.js diff --git a/src/lib/calculateDirection/calculateDirection.js b/src/lib/calculateDirection/calculateDirection.js new file mode 100644 index 0000000..5ebf113 --- /dev/null +++ b/src/lib/calculateDirection/calculateDirection.js @@ -0,0 +1,52 @@ +import { calculateCollision } from '../calculateCollision/calculateCollision'; + +/** + * Вычисляет направление наибольшего пересечения по осям и возвращает tuple множителей для изменения координат + * @param {Object} firstObject - первый объект + * @param {number} firstObject.left - Min X координата первого объекта + * @param {number} firstObject.right - Max X координата первого объекта + * @param {number} firstObject.top - Min Y координата первого объекта (ось смотрит вниз) + * @param {number} firstObject.bottom - Max Y координата первого объекта + * @param {Object} secondObject - второй объект + * @param {number} secondObject.left - Min X координата второго объекта + * @param {number} secondObject.right - Max X координата второго объекта + * @param {number} secondObject.top - Min Y координата второго объекта (ось смотрит вниз) + * @param {number} secondObject.bottom - Max Y координата второго объекта + * @returns {Array} tuple формата [1, -1] с множителями для осей X и Y. Каждый может принимать значение либо 1, либо -1 + */ +export function calculateDirection(firstObject, secondObject) { + try { + // Запускаем для проверки формата аргументов + const isCollided = calculateCollision(firstObject, secondObject); + + if (isCollided === null) { + return null; + } + + if (!isCollided) { + return [1, 1]; + } + + // Смотрим по какой оси значение пересечения объектов больше и выбираем множитель по + const valueX = Math.min(firstObject.right, secondObject.right) - Math.max(firstObject.left, secondObject.left); + const valueY = Math.min(firstObject.bottom, secondObject.bottom) - Math.max(firstObject.top, secondObject.top); + + switch (true) { + // TODO: добавить эпсилон для сравнения + case valueX === valueY: + return [-1, -1]; + + case valueX > valueY: + return [1, -1]; + + case valueX < valueY: + return [-1, 1]; + + default: + return [1, 1]; + } + } catch (err) { + console.error(err); + return null; + } +} diff --git a/src/lib/calculateDirection/calculateDirection.test.js b/src/lib/calculateDirection/calculateDirection.test.js new file mode 100644 index 0000000..879c1f0 --- /dev/null +++ b/src/lib/calculateDirection/calculateDirection.test.js @@ -0,0 +1,111 @@ +import { calculateDirection } from './calculateDirection'; + +describe('calculateDirection', () => { + it('Корректно обрабатывает невозможные кейсы (левая координата больше правой)', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 10, + right: 0, + top: 0, + bottom: 10, + }; + + expect(calculateDirection(firstObject, secondObject)).toBeNull(); + }); + + it('Корректно обрабатывает неверный формат данных', () => { + const firstObject = { + left: 'wrong', + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 0, + right: 10, + top: 0, + bottom: 10, + }; + + expect(calculateDirection(firstObject, secondObject)).toBeNull(); + }); + + it('Возвращает корректные множители для кейса с отсутствием коллизии', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 30, + right: 40, + top: 30, + bottom: 40, + }; + + expect(calculateDirection(firstObject, secondObject)).toEqual([1, 1]); + }); + + it('Возвращает корректные множители для коллизии по оси Y', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 5, + right: 20, + top: 15, + bottom: 25, + }; + + expect(calculateDirection(firstObject, secondObject)).toEqual([1, -1]); + }); + + it('Возвращает корректные множители для коллизии по оси X', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 15, + right: 20, + top: 5, + bottom: 25, + }; + + expect(calculateDirection(firstObject, secondObject)).toEqual([-1, 1]); + }); + + it('Возвращает корректные множители для коллизии по осям X и Y', () => { + const firstObject = { + left: 0, + right: 20, + top: 0, + bottom: 20, + }; + + const secondObject = { + left: 15, + right: 25, + top: 15, + bottom: 25, + }; + + expect(calculateDirection(firstObject, secondObject)).toEqual([-1, -1]); + }); +}); -- 2.54.0 From 37ed43fdfa18dc1795e773a662206a8f34daea9a Mon Sep 17 00:00:00 2001 From: Ilia Mashkov Date: Thu, 16 Jul 2026 14:14:37 +0300 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=82=D1=81=D0=BA=D0=BE=D0=BA=D0=B0=20=D0=BC=D1=8F?= =?UTF-8?q?=D1=87=D0=B0=20=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B0=20=D1=81=D0=BC=D0=B5=D1=80?= =?UTF-8?q?=D1=82=D0=B8=20=D0=BA=D0=B8=D1=80=D0=BF=D0=B8=D1=87=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main.js b/src/main.js index e0a7b27..9754c58 100644 --- a/src/main.js +++ b/src/main.js @@ -12,6 +12,7 @@ import { PADDLE_WIDTH, } from './config'; import { calculateCollision } from './lib/calculateCollision/calculateCollision'; +import { calculateDirection } from './lib/calculateDirection/calculateDirection'; (async () => { // Create a new application @@ -54,6 +55,8 @@ import { calculateCollision } from './lib/calculateCollision/calculateCollision' }); const ball = new Graphics().circle(0, 0, BALL_RADIUS).fill('#ffffff'); + ball.x = 100; + ball.y = 100; container.addChild(ball); const leftBoundary = BALL_RADIUS; @@ -80,13 +83,21 @@ import { calculateCollision } from './lib/calculateCollision/calculateCollision' const ballTop = ball.y - BALL_RADIUS; const ballBottom = ball.y + BALL_RADIUS; - const isCollided = calculateCollision( - { left: ballLeft, right: ballRight, top: ballTop, bottom: ballBottom }, - { 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 }; - if (isCollided) { - verticalSpeed *= -1; + const isCollided = calculateCollision(ballObject, brickObject); + const directions = calculateDirection(ballObject, brickObject); + + if (isCollided && directions !== null) { + horizontalSpeed *= directions[0]; + verticalSpeed *= directions[1]; + + container.removeChild(currentBrick); + currentBrick.destroy(); + bricksRow.splice(i, 1); + + break; } } -- 2.54.0