diff --git a/src/lib/processBrickCollision/processBrickCollision.js b/src/lib/processBrickCollision/processBrickCollision.js new file mode 100644 index 0000000..ef2a3cf --- /dev/null +++ b/src/lib/processBrickCollision/processBrickCollision.js @@ -0,0 +1,74 @@ +import { PERK_DROP_CHANCE, PERK_HEIGHT, PERK_WIDTH } from '../../config'; +import { Ball } from '../../entities/ball/ball'; +import { Game } from '../../game'; +import { calculateAABBCollision } from '../calculateAABBCollision/calculateAABBCollision'; +import { calculateDirection } from '../calculateDirection/calculateDirection'; +import { spawnRandomPerk } from '../spawnRandomPerk/spawnRandomPerk'; + +/** + * Базовое взаимодействие мяча и кирпичей: меняет направление, разрушает кирпич и роняет бонус + * @param {Game} game экземпляр класса игры + * @param {Ball} ball мяч + * @returns {boolean | null} true, если произошло столкновение с кирпичом + */ +export function processBrickCollision(game, ball) { + try { + if (!(game instanceof Game)) { + throw new Error('Аргумент game должен быть экземпляром класса игра'); + } + + if (!(ball instanceof Ball)) { + throw new Error('Аргумент ball должен быть экземпляром класса мяч'); + } + + for (const brick of game.bricks) { + if (!brick.alive) { + continue; + } + + const ballObject = { + left: ball.x - ball.radius, + right: ball.x + ball.radius, + top: ball.y - ball.radius, + bottom: ball.y + ball.radius, + }; + const brickObject = { + left: brick.x, + right: brick.x + brick.width, + top: brick.y, + bottom: brick.y + brick.height, + }; + + const isCollided = calculateAABBCollision(ballObject, brickObject); + + if (!isCollided) { + continue; + } + + const directions = calculateDirection(ballObject, brickObject); + + if (directions === null) { + continue; + } + + ball.horizontalSpeed *= directions[0]; + ball.verticalSpeed *= directions[1]; + brick.kill(); + + if (!brick.alive && Math.random() < PERK_DROP_CHANCE) { + const perk = spawnRandomPerk( + brick.x + brick.width / 2 - PERK_WIDTH / 2, + brick.y + brick.height / 2 - PERK_HEIGHT / 2, + ); + game.perks.push(perk); + } + + return true; + } + + return false; + } catch (err) { + console.error(err); + return null; + } +} diff --git a/src/lib/processBrickCollision/processBrickCollision.test.js b/src/lib/processBrickCollision/processBrickCollision.test.js new file mode 100644 index 0000000..e0321fb --- /dev/null +++ b/src/lib/processBrickCollision/processBrickCollision.test.js @@ -0,0 +1,33 @@ +import { Game } from '../../game'; +import { processBrickCollision } from './processBrickCollision'; + +describe('processBrickCollision', () => { + it('Возвращает корректное значение при неверных типах аргументов', () => { + const game = new Game([[[1]]]); + + expect(processBrickCollision(null, game.ball)).toBeNull(); + expect(processBrickCollision(game, null)).toBeNull(); + }); + + it('Возвращает креектное значение если мяч не касается кирпичей', () => { + const game = new Game([[[1]]]); + game.ball.x = 5; + game.ball.y = 500; + + expect(processBrickCollision(game, game.ball)).toBe(false); + }); + + it('При столкновении разрушает кирпич и меняет направление мяча', () => { + const game = new Game([[[1]]]); + 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; + game.ball.horizontalSpeed = 0; + game.ball.verticalSpeed = -10; + game.ball.moveForward(1); + + expect(processBrickCollision(game, game.ball)).toBe(true); + expect(brick.alive).toBe(false); + expect(game.ball.verticalSpeed).toBe(10); + }); +});