From 6e2f6d31f3a3b134ca783022be958454210f1a2c Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:12:44 +0300 Subject: [PATCH 01/11] some initial commit --- lab1/CMakeLists.txt | 23 ++ lab1/README.md | 26 ++- lab1/include/Block.hpp | 25 ++ lab1/include/Bonus.hpp | 14 ++ lab1/include/Game.hpp | 53 +++++ lab1/src/Block.cpp | 17 ++ lab1/src/Bonus.cpp | 13 ++ lab1/src/Game.cpp | 505 +++++++++++++++++++++++++++++++++++++++++ lab1/src/main.cpp | 9 + 9 files changed, 684 insertions(+), 1 deletion(-) create mode 100644 lab1/CMakeLists.txt create mode 100644 lab1/include/Block.hpp create mode 100644 lab1/include/Bonus.hpp create mode 100644 lab1/include/Game.hpp create mode 100644 lab1/src/Block.cpp create mode 100644 lab1/src/Bonus.cpp create mode 100644 lab1/src/Game.cpp create mode 100644 lab1/src/main.cpp diff --git a/lab1/CMakeLists.txt b/lab1/CMakeLists.txt new file mode 100644 index 0000000..230d45c --- /dev/null +++ b/lab1/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.15) +project(arkanoid) + +set(CMAKE_CXX_STANDARD 17) + +include(FetchContent) +FetchContent_Declare( + SDL3 + GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" + GIT_TAG "main" + EXCLUDE_FROM_ALL +) +FetchContent_MakeAvailable(SDL3) + +add_executable(arkanoid + src/main.cpp + src/Game.cpp + src/Block.cpp + src/Bonus.cpp +) + +target_include_directories(arkanoid PRIVATE include) +target_link_libraries(arkanoid PRIVATE SDL3::SDL3) \ No newline at end of file diff --git a/lab1/README.md b/lab1/README.md index bbaf8aa..22cb97b 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -1,2 +1,26 @@ CPP laboratories -sheesh \ No newline at end of file +sheesh + +## Чтобы собрать игру: + +```bash +mkdir build +cd build +cmake .. +cmake --build . +``` +Важно: SDL3.dll должен быть в одной папке с бинарником, SDL очень топит за dynamic link +Если что, CMake fetch content сам скачает и соберет всё что нужно за нас, но .dll куда надо не положит + + +## Управление: +1. Стрелки влево/вправо - движение каретки +2. R - рестарт игры +4. Также каретка следует за движениями мышью + +## Эффекты бонусов: + +- Увеличение/уменьшение каретки и скорости мяча - мгновенное +- Липкая каретка - действует 10 секунд +- Защитная сетка - действует 10 секунд ('одноразовое дно для шарика' из условия) +- Случайное направление - мгновенное изменение траектории diff --git a/lab1/include/Block.hpp b/lab1/include/Block.hpp new file mode 100644 index 0000000..799344f --- /dev/null +++ b/lab1/include/Block.hpp @@ -0,0 +1,25 @@ +#pragma once +#include + +enum class BonusType { + None, + ExpandPaddle, + ShrinkPaddle, + SpeedUp, + StickyPaddle, + SafetyNet, + RandomDirection +}; + +struct Block { + SDL_FRect rect; + int health; + bool indestructible; + BonusType bonus; + bool hasBonus; + SDL_Color color; + + Block(float x, float y, float w, float h, int hp, bool indestruct, BonusType bonusType); + void Hit(); + bool IsDestroyed() const; +}; \ No newline at end of file diff --git a/lab1/include/Bonus.hpp b/lab1/include/Bonus.hpp new file mode 100644 index 0000000..4a582e8 --- /dev/null +++ b/lab1/include/Bonus.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include "Block.hpp" + +struct Bonus { + SDL_FRect rect; + BonusType type; + bool active; + float fallSpeed; + + Bonus(float x, float y, BonusType t); + void Update(float deltaTime); + void Activate(class Game& game); +}; \ No newline at end of file diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp new file mode 100644 index 0000000..1e3aef1 --- /dev/null +++ b/lab1/include/Game.hpp @@ -0,0 +1,53 @@ +#pragma once +#include +#include +#include +#include "Block.hpp" +#include "Bonus.hpp" + +class Game { +public: + Game(); + ~Game(); + + bool Initialize(); + void Run(); + void ActivateBonus(BonusType type); + void ResetGame(); + +private: + void ProcessInput(); + void Update(float deltaTime); + void UpdateBonuses(float deltaTime); + void UpdatePaddle(float deltaTime); + void Render(); + void CheckCollisions(); + void HandleBallBlockCollision(Block& block); + void SpawnBonus(float x, float y, BonusType type); + void HandleBlockHit(Block& block); + + SDL_Window* window; + SDL_Renderer* renderer; + bool isRunning; + bool needsReset; + + // Game objects + SDL_FRect paddle; + SDL_FRect ball; + SDL_FPoint ballVelocity; + + std::vector> blocks; + std::vector> activeBonuses; + + // Game state + int score; + int lives; + bool stickyPaddle; + bool safetyNetActive; + float originalPaddleWidth; + float paddleSpeedMultiplier; + float ballSpeedMultiplier; + const float maxBallSpeed = 500; // Максимальная скорость мяча + const float ballRadius = ball.w / 2.0f; // Радиус для точных расчетов + Uint64 safetyNetExpireTime; +}; \ No newline at end of file diff --git a/lab1/src/Block.cpp b/lab1/src/Block.cpp new file mode 100644 index 0000000..17a1c2d --- /dev/null +++ b/lab1/src/Block.cpp @@ -0,0 +1,17 @@ +#include "Block.hpp" + +Block::Block(float x, float y, float w, float h, int hp, bool indestruct, BonusType bonusType) : + rect{x, y, w, h}, + health(hp), + indestructible(indestruct), + bonus(bonusType), + hasBonus(bonusType != BonusType::None), + color{128, 128, 128, 255} {} + +void Block::Hit() { + if(!indestructible) health--; +} + +bool Block::IsDestroyed() const { + return health <= 0 && !indestructible; +} \ No newline at end of file diff --git a/lab1/src/Bonus.cpp b/lab1/src/Bonus.cpp new file mode 100644 index 0000000..9f49771 --- /dev/null +++ b/lab1/src/Bonus.cpp @@ -0,0 +1,13 @@ +#include "Bonus.hpp" +#include "Game.hpp" + +Bonus::Bonus(float x, float y, BonusType t) : + rect{x, y, 30, 15}, type(t), active(true), fallSpeed(0.2f) {} + +void Bonus::Update(float deltaTime) { + rect.y += fallSpeed * deltaTime; +} + +void Bonus::Activate(Game& game) { + game.ActivateBonus(type); +} \ No newline at end of file diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp new file mode 100644 index 0000000..60a87fb --- /dev/null +++ b/lab1/src/Game.cpp @@ -0,0 +1,505 @@ +#include "Game.hpp" +#include +#include + +const int SCREEN_WIDTH = 800; +const int SCREEN_HEIGHT = 600; +const float BASE_PADDLE_SPEED = 200.0f; +const float BASE_BALL_SPEED = 100.0f; + +Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), + score(0), lives(3), stickyPaddle(false), safetyNetActive(false), + originalPaddleWidth(100), paddleSpeedMultiplier(1.0f), + ballSpeedMultiplier(1.0f), safetyNetExpireTime(0) {} + +void Game::ResetGame() { + // Reset game state + lives = 3; + score = 0; + stickyPaddle = false; + safetyNetActive = false; + paddleSpeedMultiplier = 1.0f; + ballSpeedMultiplier = 1.0f; + + // Reset paddle and ball + paddle = {SCREEN_WIDTH / 2 - originalPaddleWidth / 2, SCREEN_HEIGHT - 40, originalPaddleWidth, 20}; + ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; + ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; + + // Rebuild blocks + blocks.clear(); + const int rows = 4; + const int cols = 10; + const float blockWidth = 70; + const float blockHeight = 20; + const float spacing = 5; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> bonusDist(1, 6); + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + float x = j * (blockWidth + spacing) + spacing; + float y = i * (blockHeight + spacing) + 50; + BonusType bonus = BonusType::None; + bool indestruct = (i == 0); + int health = indestruct ? 1 : (rows - i); + + if (!indestruct && (gen() % 4) == 0) { + bonus = static_cast(bonusDist(gen)); + } + + blocks.emplace_back(std::make_unique( + x, y, blockWidth, blockHeight, + health, indestruct, bonus + )); + } + } + + activeBonuses.clear(); +} + +void Game::CheckCollisions() { + // Обработка границ экрана + if (ball.x < 0) { + ball.x = 0; + ballVelocity.x = fabs(ballVelocity.x); + } + if (ball.x + ball.w > SCREEN_WIDTH) { + ball.x = SCREEN_WIDTH - ball.w; + ballVelocity.x = -fabs(ballVelocity.x); + } + if (ball.y < 0) { + ball.y = 0; + ballVelocity.y = fabs(ballVelocity.y); + } + + // Коллизия с кареткой + SDL_FRect intersection; + if (SDL_GetRectIntersectionFloat(&ball, &paddle, &intersection)) { + // Корректировка позиции мяча + ball.y = paddle.y - ball.h; + + // Расчет точки удара [-1, 1] + float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w * 2 - 1; + ballVelocity.x = hitPosition * fabs(ballVelocity.x + ballVelocity.y); + ballVelocity.y = -fabs(ballVelocity.y); + } + + // Коллизия с блоками + for (auto it = blocks.begin(); it != blocks.end();) { + SDL_FRect blockRect = (*it)->rect; + SDL_FRect intersect; + + if (SDL_GetRectIntersectionFloat(&ball, &blockRect, &intersect)) { + // Определение направления отскока + float overlapLeft = ball.x + ball.w - blockRect.x; + float overlapRight = blockRect.x + blockRect.w - ball.x; + float overlapTop = ball.y + ball.h - blockRect.y; + float overlapBottom = blockRect.y + blockRect.h - ball.y; + + bool horizontalCollision = std::min(overlapLeft, overlapRight) < + std::min(overlapTop, overlapBottom); + + if (horizontalCollision) { + ballVelocity.x = (overlapLeft < overlapRight) ? -fabs(ballVelocity.x) : fabs(ballVelocity.x); + ball.x += (overlapLeft < overlapRight) ? -intersect.w : intersect.w; + } + else { + ballVelocity.y = (overlapTop < overlapBottom) ? -fabs(ballVelocity.y) : fabs(ballVelocity.y); + ball.y += (overlapTop < overlapBottom) ? -intersect.h : intersect.h; + } + + // Обработка удара по блоку + if (!(*it)->indestructible) { + (*it)->health--; + score++; + + if ((*it)->health <= 0) { + if ((*it)->hasBonus) { + SpawnBonus(blockRect.x + blockRect.w / 2, + blockRect.y + blockRect.h / 2, + (*it)->bonus); + } + it = blocks.erase(it); + continue; + } + } + } + ++it; + } + + // Коллизия бонусов с кареткой + for (auto it = activeBonuses.begin(); it != activeBonuses.end();) { + SDL_FRect bonusRect = (*it)->rect; + if (SDL_GetRectIntersectionFloat(&bonusRect, &paddle, &intersection)) { + ActivateBonus((*it)->type); + it = activeBonuses.erase(it); + } + else { + if (bonusRect.y > SCREEN_HEIGHT) it = activeBonuses.erase(it); + else ++it; + } + } +} + +void Game::HandleBlockHit(Block& block) { + if (!block.indestructible) { + block.Hit(); + score += 1; // +1 очко за попадание + } + + // Изменение скорости мяча для специальных блоков + if (block.health == 3) { + // Пример для блока, увеличивающего скорость + ballSpeedMultiplier *= 1.2f; + } +} + + +void Game::HandleBallBlockCollision(Block& block) { + block.Hit(); + + // Calculate collision normal + float ballCenterX = ball.x + ball.w / 2; + float ballCenterY = ball.y + ball.h / 2; + float blockCenterX = block.rect.x + block.rect.w / 2; + float blockCenterY = block.rect.y + block.rect.h / 2; + + float dx = ballCenterX - blockCenterX; + float dy = ballCenterY - blockCenterY; + float absDx = fabs(dx); + float absDy = fabs(dy); + + if (absDx > absDy) { + // Horizontal collision + ballVelocity.x *= -1; + } + else { + // Vertical collision + ballVelocity.y *= -1; + } +} + +void Game::ActivateBonus(BonusType type) { + switch (type) { + case BonusType::ExpandPaddle: + paddle.w = originalPaddleWidth * 1.5f; + break; + + case BonusType::ShrinkPaddle: + paddle.w = originalPaddleWidth * 0.75f; + break; + + case BonusType::SpeedUp: + ballSpeedMultiplier *= 1.2f; + break; + + case BonusType::StickyPaddle: + stickyPaddle = true; + safetyNetExpireTime = SDL_GetTicks() + 10000; // 10 секунд + break; + + case BonusType::SafetyNet: + safetyNetActive = true; + safetyNetExpireTime = SDL_GetTicks() + 10000; // 10 seconds + break; + + case BonusType::RandomDirection: { + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution angleDist(-0.5f, 0.5f); + ballVelocity.x += angleDist(gen); + ballVelocity.y += angleDist(gen); + // Normalize speed + float speed = sqrtf(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y); + ballVelocity.x = (ballVelocity.x / speed) * BASE_BALL_SPEED * ballSpeedMultiplier; + ballVelocity.y = (ballVelocity.y / speed) * BASE_BALL_SPEED * ballSpeedMultiplier; + break; + } + + default: + break; + } +} + +void Game::SpawnBonus(float x, float y, BonusType type) { + auto bonus = std::make_unique(x, y, type); + activeBonuses.push_back(std::move(bonus)); +} + + +void Game::Run() { + Uint64 previousTicks = SDL_GetTicks(); + + while (isRunning) { + Uint64 currentTicks = SDL_GetTicks(); + float deltaTime = (currentTicks - previousTicks) / 1000.0f; + previousTicks = currentTicks; + + ProcessInput(); + Update(deltaTime); + Render(); + } +} + +void Game::UpdatePaddle(const float deltaTime) { + // Клавиатурный ввод для движения каретки + const bool* keyboardState = SDL_GetKeyboardState(nullptr); + float moveSpeed = BASE_PADDLE_SPEED * paddleSpeedMultiplier * deltaTime; + + if (keyboardState[SDL_SCANCODE_LEFT]) { + paddle.x -= moveSpeed; + } + if (keyboardState[SDL_SCANCODE_RIGHT]) { + paddle.x += moveSpeed; + } + + // Ограничение движения каретки + if (paddle.x < 0) paddle.x = 0; + if (paddle.x > SCREEN_WIDTH - paddle.w) { + paddle.x = SCREEN_WIDTH - paddle.w; + } +} + +void Game::UpdateBonuses(const float deltaTime) { + // Движение бонусов вниз + for (auto& bonus : activeBonuses) { + bonus->rect.y += 150.0f * deltaTime; // Скорость падения + if (bonus->rect.y > SCREEN_HEIGHT) { + bonus->active = false; + } + } + + // Удаление неактивных бонусов + activeBonuses.erase( + std::remove_if(activeBonuses.begin(), activeBonuses.end(), + [](const auto& b) { return !b->active; }), + activeBonuses.end() + ); + + // Обновление бонусов + for (auto& bonus : activeBonuses) { + bonus->Update(deltaTime); + } + + // Проверка времени действия бонусов + Uint64 currentTime = SDL_GetTicks(); + if (stickyPaddle && currentTime > safetyNetExpireTime) { + stickyPaddle = false; + } + if (safetyNetActive && currentTime > safetyNetExpireTime) { + safetyNetActive = false; + } +} + +void Game::Update(const float deltaTime) { + // Обновление позиции мяча с учётом множителя скорости + ball.x += ballVelocity.x * deltaTime * ballSpeedMultiplier; + ball.y += ballVelocity.y * deltaTime * ballSpeedMultiplier; + + // Проверка поражения + if (ball.y + ball.h > SCREEN_HEIGHT) { + if (safetyNetActive) { + // Отскок от защитной сетки + ballVelocity.y = -fabs(ballVelocity.y); + safetyNetActive = false; + } + else { + lives--; + if (lives > 0) { + // Респавн мяча + ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; + ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; + } + else { + ResetGame(); + } + } + } + + CheckCollisions(); + UpdateBonuses(deltaTime); + UpdatePaddle(deltaTime); + + // Прилипание мяча к каретке + if (stickyPaddle) { + ball.x = paddle.x + paddle.w / 2 - ball.w / 2; + ball.y = paddle.y - ball.h; + } + + // Удаление разрушенных блоков + blocks.erase( + std::remove_if(blocks.begin(), blocks.end(), + [](const auto& block) { return block->IsDestroyed(); }), + blocks.end() + ); +} + +void Game::Render() { + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderClear(renderer); + + // Отрисовка каретки + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderFillRect(renderer, &paddle); + + // Отрисовка мяча + SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); + SDL_RenderFillRect(renderer, &ball); + + // Отрисовка блоков + for (const auto& block : blocks) { + SDL_SetRenderDrawColor(renderer, + block->color.r, + block->color.g, + block->color.b, + block->color.a + ); + SDL_RenderFillRect(renderer, &block->rect); + + // Для неразрушаемых блоков добавляем рамку + if (block->indestructible) { + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &block->rect); + } + } + + // Отрисовка бонусов + for (const auto& bonus : activeBonuses) { + SDL_Color bonusColor; + switch (bonus->type) { + case BonusType::ExpandPaddle: bonusColor = {0, 255, 0, 255}; + break; + case BonusType::ShrinkPaddle: bonusColor = {255, 0, 0, 255}; + break; + case BonusType::SpeedUp: bonusColor = {255, 255, 0, 255}; + break; + case BonusType::StickyPaddle: bonusColor = {0, 255, 255, 255}; + break; + case BonusType::SafetyNet: bonusColor = {255, 165, 0, 255}; + break; + case BonusType::RandomDirection: bonusColor = {255, 0, 255, 255}; + break; + default: bonusColor = {255, 255, 255, 255}; + } + SDL_SetRenderDrawColor(renderer, + bonusColor.r, bonusColor.g, bonusColor.b, bonusColor.a); + SDL_RenderFillRect(renderer, &bonus->rect); + } + + // Отрисовка HUD + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + // SDL_FRect bottomLine = {0, SCREEN_HEIGHT - 2, SCREEN_WIDTH, 2}; + if (safetyNetActive) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + SDL_RenderLine(renderer, 0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, SCREEN_HEIGHT - 20); + } + + SDL_RenderPresent(renderer); +} + +void Game::ProcessInput() { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_EVENT_QUIT: + isRunning = false; + break; + + case SDL_EVENT_KEY_DOWN: + // Рестарт игры по R + if (event.key.key == SDLK_R) { + ResetGame(); + } + // Отпускание мяча при липкой каретке + if (stickyPaddle && event.key.key == SDLK_SPACE) { + stickyPaddle = false; + ballVelocity = { + BASE_BALL_SPEED * ballSpeedMultiplier, + -BASE_BALL_SPEED * ballSpeedMultiplier + }; + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + // Отпускание мяча по клику мыши + if (stickyPaddle) { + stickyPaddle = false; + ballVelocity = { + BASE_BALL_SPEED * ballSpeedMultiplier, + -BASE_BALL_SPEED * ballSpeedMultiplier + }; + } + break; + + case SDL_EVENT_MOUSE_MOTION: + // Управление кареткой мышью + if (!stickyPaddle) { + paddle.x = event.motion.x - paddle.w / 2; + } + break; + default: continue; + } + } +} + +bool Game::Initialize() { + // Инициализация SDL + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_Log("Failed to initialize SDL: %s", SDL_GetError()); + return false; + } + + // Создание окна + window = SDL_CreateWindow("Arkanoid", + SCREEN_WIDTH, + SCREEN_HEIGHT, + SDL_WINDOW_RESIZABLE); + if (!window) { + SDL_Log("Failed to create window: %s", SDL_GetError()); + SDL_Quit(); + return false; + } + + // Создание рендерера + renderer = SDL_CreateRenderer(window, + nullptr); + if (!renderer) { + SDL_Log("Failed to create renderer: %s", SDL_GetError()); + SDL_DestroyWindow(window); + SDL_Quit(); + return false; + } + + // Инициализация игрового состояния + originalPaddleWidth = 100.0f; + paddleSpeedMultiplier = 1.0f; + ballSpeedMultiplier = 1.0f; + safetyNetExpireTime = 0; + stickyPaddle = false; + safetyNetActive = false; + + // Настройка рандомизации + std::srand(static_cast(SDL_GetTicks())); + + // Инициализация игровых объектов + ResetGame(); + + // Начальные координаты каретки + paddle = { + SCREEN_WIDTH / 2 - originalPaddleWidth / 2, + SCREEN_HEIGHT - 40, + originalPaddleWidth, + 20 + }; + + // Начальное состояние мяча + ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; + ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; + + return true; +} + +Game::~Game() = default; diff --git a/lab1/src/main.cpp b/lab1/src/main.cpp new file mode 100644 index 0000000..04e3699 --- /dev/null +++ b/lab1/src/main.cpp @@ -0,0 +1,9 @@ +#include "Game.hpp" + +int main() { + Game game; + if(game.Initialize()) { + game.Run(); + } + return 0; +} \ No newline at end of file From 18094ec4d08de4d1cbeb217240df2c8f2776aecc Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:15:25 +0300 Subject: [PATCH 02/11] moved lives amount to constexpr Moved all consts to constexprs --- lab1/src/Game.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 60a87fb..8e0e778 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -2,19 +2,20 @@ #include #include -const int SCREEN_WIDTH = 800; -const int SCREEN_HEIGHT = 600; -const float BASE_PADDLE_SPEED = 200.0f; -const float BASE_BALL_SPEED = 100.0f; +constexpr int SCREEN_WIDTH = 800; +constexpr int SCREEN_HEIGHT = 600; +constexpr float BASE_PADDLE_SPEED = 200.0f; +constexpr float BASE_BALL_SPEED = 100.0f; +constexpr int LIVES = 1; Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), - score(0), lives(3), stickyPaddle(false), safetyNetActive(false), + score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), originalPaddleWidth(100), paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f), safetyNetExpireTime(0) {} void Game::ResetGame() { // Reset game state - lives = 3; + lives = LIVES; score = 0; stickyPaddle = false; safetyNetActive = false; From 17c24a0f2822fac993f53c5f3efa83cdb7e77f8b Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:20:49 +0300 Subject: [PATCH 03/11] Moved more magical values into consts --- lab1/include/Game.hpp | 2 +- lab1/src/Game.cpp | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 1e3aef1..91d5e9f 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -44,10 +44,10 @@ class Game { int lives; bool stickyPaddle; bool safetyNetActive; - float originalPaddleWidth; float paddleSpeedMultiplier; float ballSpeedMultiplier; const float maxBallSpeed = 500; // Максимальная скорость мяча const float ballRadius = ball.w / 2.0f; // Радиус для точных расчетов Uint64 safetyNetExpireTime; + Uint64 stickyPaddleExpireTime; }; \ No newline at end of file diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 8e0e778..f211063 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -5,13 +5,16 @@ constexpr int SCREEN_WIDTH = 800; constexpr int SCREEN_HEIGHT = 600; constexpr float BASE_PADDLE_SPEED = 200.0f; +constexpr int BASE_PADDLE_WIDTH = 100; +constexpr int BASE_PADDLE_HEIGHT = 20; constexpr float BASE_BALL_SPEED = 100.0f; constexpr int LIVES = 1; +constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s +constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), - originalPaddleWidth(100), paddleSpeedMultiplier(1.0f), - ballSpeedMultiplier(1.0f), safetyNetExpireTime(0) {} + paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f) {} void Game::ResetGame() { // Reset game state @@ -23,7 +26,10 @@ void Game::ResetGame() { ballSpeedMultiplier = 1.0f; // Reset paddle and ball - paddle = {SCREEN_WIDTH / 2 - originalPaddleWidth / 2, SCREEN_HEIGHT - 40, originalPaddleWidth, 20}; + paddle = { + SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, BASE_PADDLE_WIDTH, + BASE_PADDLE_HEIGHT + }; ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; @@ -186,11 +192,11 @@ void Game::HandleBallBlockCollision(Block& block) { void Game::ActivateBonus(BonusType type) { switch (type) { case BonusType::ExpandPaddle: - paddle.w = originalPaddleWidth * 1.5f; + paddle.w = BASE_PADDLE_WIDTH * 1.5f; break; case BonusType::ShrinkPaddle: - paddle.w = originalPaddleWidth * 0.75f; + paddle.w = BASE_PADDLE_WIDTH * 0.75f; break; case BonusType::SpeedUp: @@ -199,12 +205,12 @@ void Game::ActivateBonus(BonusType type) { case BonusType::StickyPaddle: stickyPaddle = true; - safetyNetExpireTime = SDL_GetTicks() + 10000; // 10 секунд + stickyPaddleExpireTime = SDL_GetTicks() + STICKY_PADDLE_DURATION; break; case BonusType::SafetyNet: safetyNetActive = true; - safetyNetExpireTime = SDL_GetTicks() + 10000; // 10 seconds + safetyNetExpireTime = SDL_GetTicks() + SAFETY_NET_DURATION; break; case BonusType::RandomDirection: { @@ -475,7 +481,6 @@ bool Game::Initialize() { } // Инициализация игрового состояния - originalPaddleWidth = 100.0f; paddleSpeedMultiplier = 1.0f; ballSpeedMultiplier = 1.0f; safetyNetExpireTime = 0; @@ -490,10 +495,10 @@ bool Game::Initialize() { // Начальные координаты каретки paddle = { - SCREEN_WIDTH / 2 - originalPaddleWidth / 2, - SCREEN_HEIGHT - 40, - originalPaddleWidth, - 20 + SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, + SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, + BASE_PADDLE_WIDTH, + BASE_PADDLE_HEIGHT }; // Начальное состояние мяча From 6e1488b9bfb446961ffbdcf26360f08f9608c054 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:33:10 +0300 Subject: [PATCH 04/11] fixed safety net now ball is sticked to paddle at the start of game --- lab1/include/Game.hpp | 4 ++-- lab1/src/Game.cpp | 16 +++++++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 91d5e9f..533a19b 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -48,6 +48,6 @@ class Game { float ballSpeedMultiplier; const float maxBallSpeed = 500; // Максимальная скорость мяча const float ballRadius = ball.w / 2.0f; // Радиус для точных расчетов - Uint64 safetyNetExpireTime; - Uint64 stickyPaddleExpireTime; + Uint64 safetyNetExpireTime = 0; + Uint64 stickyPaddleExpireTime = 0; }; \ No newline at end of file diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index f211063..4ca558a 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -7,20 +7,20 @@ constexpr int SCREEN_HEIGHT = 600; constexpr float BASE_PADDLE_SPEED = 200.0f; constexpr int BASE_PADDLE_WIDTH = 100; constexpr int BASE_PADDLE_HEIGHT = 20; -constexpr float BASE_BALL_SPEED = 100.0f; +constexpr float BASE_BALL_SPEED = 200.0f; constexpr int LIVES = 1; constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), - score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), + score(0), lives(LIVES), stickyPaddle(true), safetyNetActive(false), paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f) {} void Game::ResetGame() { // Reset game state lives = LIVES; score = 0; - stickyPaddle = false; + stickyPaddle = true; safetyNetActive = false; paddleSpeedMultiplier = 1.0f; ballSpeedMultiplier = 1.0f; @@ -293,10 +293,10 @@ void Game::UpdateBonuses(const float deltaTime) { // Проверка времени действия бонусов Uint64 currentTime = SDL_GetTicks(); - if (stickyPaddle && currentTime > safetyNetExpireTime) { + if (stickyPaddle && stickyPaddleExpireTime && currentTime > stickyPaddleExpireTime) { stickyPaddle = false; } - if (safetyNetActive && currentTime > safetyNetExpireTime) { + if (safetyNetActive && safetyNetExpireTime && currentTime > safetyNetExpireTime) { safetyNetActive = false; } } @@ -443,9 +443,7 @@ void Game::ProcessInput() { case SDL_EVENT_MOUSE_MOTION: // Управление кареткой мышью - if (!stickyPaddle) { - paddle.x = event.motion.x - paddle.w / 2; - } + paddle.x = event.motion.x - paddle.w / 2; break; default: continue; } @@ -484,7 +482,7 @@ bool Game::Initialize() { paddleSpeedMultiplier = 1.0f; ballSpeedMultiplier = 1.0f; safetyNetExpireTime = 0; - stickyPaddle = false; + stickyPaddle = true; safetyNetActive = false; // Настройка рандомизации From 2dfb96d1918f426546bb6550e437c8469dcc376e Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:43:36 +0300 Subject: [PATCH 05/11] changed paddle collision logic to change ball's velocity more realistically --- lab1/src/Game.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 4ca558a..db84be4 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -1,6 +1,7 @@ #include "Game.hpp" #include #include +#include constexpr int SCREEN_WIDTH = 800; constexpr int SCREEN_HEIGHT = 600; @@ -90,8 +91,9 @@ void Game::CheckCollisions() { // Расчет точки удара [-1, 1] float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w * 2 - 1; - ballVelocity.x = hitPosition * fabs(ballVelocity.x + ballVelocity.y); - ballVelocity.y = -fabs(ballVelocity.y); + float prevVelocity = fabs(ballVelocity.x) + fabs(ballVelocity.y); + ballVelocity.x = hitPosition * prevVelocity; + ballVelocity.y = -(1-fabs(hitPosition))*prevVelocity; } // Коллизия с блоками @@ -159,7 +161,6 @@ void Game::HandleBlockHit(Block& block) { // Изменение скорости мяча для специальных блоков if (block.health == 3) { - // Пример для блока, увеличивающего скорость ballSpeedMultiplier *= 1.2f; } } From 83401092ff68037ef0a06238d34f6febb5a30857 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 16 May 2025 18:46:18 +0300 Subject: [PATCH 06/11] changed sticky paddle logic --- lab1/include/Game.hpp | 1 + lab1/src/Game.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 533a19b..060efcc 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -44,6 +44,7 @@ class Game { int lives; bool stickyPaddle; bool safetyNetActive; + bool ballSticked = false; float paddleSpeedMultiplier; float ballSpeedMultiplier; const float maxBallSpeed = 500; // Максимальная скорость мяча diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index db84be4..d73c360 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -14,7 +14,7 @@ constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), - score(0), lives(LIVES), stickyPaddle(true), safetyNetActive(false), + score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f) {} void Game::ResetGame() { @@ -89,11 +89,13 @@ void Game::CheckCollisions() { // Корректировка позиции мяча ball.y = paddle.y - ball.h; - // Расчет точки удара [-1, 1] - float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w * 2 - 1; - float prevVelocity = fabs(ballVelocity.x) + fabs(ballVelocity.y); - ballVelocity.x = hitPosition * prevVelocity; - ballVelocity.y = -(1-fabs(hitPosition))*prevVelocity; + if (!stickyPaddle) { + // Расчет точки удара [-1, 1] + float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w * 2 - 1; + float prevVelocity = fabs(ballVelocity.x) + fabs(ballVelocity.y); + ballVelocity.x = hitPosition * prevVelocity; + ballVelocity.y = -(1 - fabs(hitPosition)) * prevVelocity; + } else ballSticked = true; } // Коллизия с блоками @@ -332,7 +334,7 @@ void Game::Update(const float deltaTime) { UpdatePaddle(deltaTime); // Прилипание мяча к каретке - if (stickyPaddle) { + if (ballSticked) { ball.x = paddle.x + paddle.w / 2 - ball.w / 2; ball.y = paddle.y - ball.h; } From 6ecd303e3c2af3bdd38b18cad68170d51038b9b1 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 23 May 2025 13:03:09 +0300 Subject: [PATCH 07/11] Minor changes: moved ball dimensions to constexpr fixed some with stycky paddle bonus Now sticky paddle rendered green --- lab1/README.md | 5 +++-- lab1/src/Game.cpp | 55 +++++++++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 23 deletions(-) diff --git a/lab1/README.md b/lab1/README.md index 22cb97b..0750186 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -15,8 +15,9 @@ cmake --build . ## Управление: 1. Стрелки влево/вправо - движение каретки -2. R - рестарт игры -4. Также каретка следует за движениями мышью +2. Также каретка следует за движениями мышью +3. R - рестарт игры +4. SPACE или нажатие любой кнопки мыши - отпустить прилипший к каретке мяч (он изначально прилипший) ## Эффекты бонусов: diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index d73c360..6c8b112 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -8,7 +8,9 @@ constexpr int SCREEN_HEIGHT = 600; constexpr float BASE_PADDLE_SPEED = 200.0f; constexpr int BASE_PADDLE_WIDTH = 100; constexpr int BASE_PADDLE_HEIGHT = 20; -constexpr float BASE_BALL_SPEED = 200.0f; +constexpr float BASE_BALL_SPEED = 300.0f; +constexpr float BASE_BALL_HEIGHT = 20; +constexpr float BASE_BALL_WIDTH = 20; constexpr int LIVES = 1; constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s @@ -21,18 +23,24 @@ void Game::ResetGame() { // Reset game state lives = LIVES; score = 0; - stickyPaddle = true; + stickyPaddle = false; + ballSticked = true; safetyNetActive = false; paddleSpeedMultiplier = 1.0f; ballSpeedMultiplier = 1.0f; + safetyNetExpireTime = 0; + stickyPaddleExpireTime = 0; // Reset paddle and ball paddle = { SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, BASE_PADDLE_WIDTH, BASE_PADDLE_HEIGHT }; - ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; - ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; + ball = { + SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_WIDTH / 2, BASE_BALL_WIDTH, + BASE_BALL_HEIGHT + }; + ballVelocity = {0, -BASE_BALL_SPEED}; // Rebuild blocks blocks.clear(); @@ -95,7 +103,11 @@ void Game::CheckCollisions() { float prevVelocity = fabs(ballVelocity.x) + fabs(ballVelocity.y); ballVelocity.x = hitPosition * prevVelocity; ballVelocity.y = -(1 - fabs(hitPosition)) * prevVelocity; - } else ballSticked = true; + } + else { + ballSticked = true; + stickyPaddle = false; + } } // Коллизия с блоками @@ -320,8 +332,12 @@ void Game::Update(const float deltaTime) { lives--; if (lives > 0) { // Респавн мяча - ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; - ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; + ball = { + SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_HEIGHT / 2, + BASE_BALL_WIDTH, BASE_BALL_HEIGHT + }; + ballVelocity = {0, -BASE_BALL_SPEED}; + ballSticked = true; } else { ResetGame(); @@ -352,7 +368,7 @@ void Game::Render() { SDL_RenderClear(renderer); // Отрисовка каретки - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_SetRenderDrawColor(renderer, stickyPaddle ? 0 : 255, 255, stickyPaddle ? 0 : 255, 255); SDL_RenderFillRect(renderer, &paddle); // Отрисовка мяча @@ -424,23 +440,17 @@ void Game::ProcessInput() { ResetGame(); } // Отпускание мяча при липкой каретке - if (stickyPaddle && event.key.key == SDLK_SPACE) { - stickyPaddle = false; - ballVelocity = { - BASE_BALL_SPEED * ballSpeedMultiplier, - -BASE_BALL_SPEED * ballSpeedMultiplier - }; + else if (ballSticked && event.key.key == SDLK_SPACE) { + ballSticked = false; + ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; } break; case SDL_EVENT_MOUSE_BUTTON_DOWN: // Отпускание мяча по клику мыши - if (stickyPaddle) { - stickyPaddle = false; - ballVelocity = { - BASE_BALL_SPEED * ballSpeedMultiplier, - -BASE_BALL_SPEED * ballSpeedMultiplier - }; + if (ballSticked) { + ballSticked = false; + ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; } break; @@ -503,7 +513,10 @@ bool Game::Initialize() { }; // Начальное состояние мяча - ball = {SCREEN_WIDTH / 2 - 10, SCREEN_HEIGHT / 2 - 10, 20, 20}; + ball = { + SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_HEIGHT / 2, + BASE_BALL_WIDTH, BASE_BALL_HEIGHT + }; ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; return true; From dd0c793a26488ea7b40006ce18c9aaa5b4980da7 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 23 May 2025 13:16:34 +0300 Subject: [PATCH 08/11] Changed ball with paddle collision logic More accurate after collision ball velocity --- lab1/src/Game.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 6c8b112..4cd1c4c 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -98,11 +98,11 @@ void Game::CheckCollisions() { ball.y = paddle.y - ball.h; if (!stickyPaddle) { - // Расчет точки удара [-1, 1] - float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w * 2 - 1; - float prevVelocity = fabs(ballVelocity.x) + fabs(ballVelocity.y); + // Расчет точки удара [-0.5, 0.5] + float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w - 0.5f; + float prevVelocity = sqrtf(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y); ballVelocity.x = hitPosition * prevVelocity; - ballVelocity.y = -(1 - fabs(hitPosition)) * prevVelocity; + ballVelocity.y = -sqrtf(prevVelocity * prevVelocity - ballVelocity.x * ballVelocity.x); } else { ballSticked = true; From 496d1212a426c622ca0798751a52c9d89781d858 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 23 May 2025 13:18:10 +0300 Subject: [PATCH 09/11] Added game quit on escape button --- lab1/README.md | 8 ++++---- lab1/src/Game.cpp | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lab1/README.md b/lab1/README.md index 0750186..560da98 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -14,11 +14,11 @@ cmake --build . ## Управление: -1. Стрелки влево/вправо - движение каретки -2. Также каретка следует за движениями мышью -3. R - рестарт игры +1. Стрелки влево/вправо — движение каретки +2. Также каретка следует за мышью +3. R — рестарт игры 4. SPACE или нажатие любой кнопки мыши - отпустить прилипший к каретке мяч (он изначально прилипший) - +5. ESCAPE - выйти из игры ## Эффекты бонусов: - Увеличение/уменьшение каретки и скорости мяча - мгновенное diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 4cd1c4c..39f56fd 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -443,6 +443,8 @@ void Game::ProcessInput() { else if (ballSticked && event.key.key == SDLK_SPACE) { ballSticked = false; ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; + } else if (event.key.key == SDLK_ESCAPE) { + isRunning=false; } break; From 648e0f049e40dc33722a546d486b4833e7f34a71 Mon Sep 17 00:00:00 2001 From: Fromant Date: Fri, 23 May 2025 13:34:56 +0300 Subject: [PATCH 10/11] Added blocks that accelerate ball --- lab1/README.md | 13 ++++++++----- lab1/include/Block.hpp | 3 ++- lab1/src/Block.cpp | 7 ++++--- lab1/src/Game.cpp | 28 ++++++++++++++++++++-------- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/lab1/README.md b/lab1/README.md index 560da98..41372d5 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -12,6 +12,8 @@ cmake --build . Важно: SDL3.dll должен быть в одной папке с бинарником, SDL очень топит за dynamic link Если что, CMake fetch content сам скачает и соберет всё что нужно за нас, но .dll куда надо не положит +У неразрушаемых блоков есть белая рамка +У блоков, ускоряющих мяч есть зеленая рамка ## Управление: 1. Стрелки влево/вправо — движение каретки @@ -19,9 +21,10 @@ cmake --build . 3. R — рестарт игры 4. SPACE или нажатие любой кнопки мыши - отпустить прилипший к каретке мяч (он изначально прилипший) 5. ESCAPE - выйти из игры +6. ## Эффекты бонусов: - -- Увеличение/уменьшение каретки и скорости мяча - мгновенное -- Липкая каретка - действует 10 секунд -- Защитная сетка - действует 10 секунд ('одноразовое дно для шарика' из условия) -- Случайное направление - мгновенное изменение траектории +- Увеличение/уменьшение каретки и скорости мяча (мгновенное) (увеличение - голубой цвет бонуса, уменьшение - красный) +- Ускорение мяча (мгновенное) (жёлтый цвет) +- Липкая каретка (действует 10 секунд) (зелёный цвет. Также меняет цвет каретки на зелёный) +- Защитная сетка (действует 10 секунд) ('одноразовое дно для шарика' из условия) (Оранжевый цвет бонуса) +- Случайное направление (мгновенное изменение траектории) (Фиолетовый цвет бонуса) diff --git a/lab1/include/Block.hpp b/lab1/include/Block.hpp index 799344f..2ab4163 100644 --- a/lab1/include/Block.hpp +++ b/lab1/include/Block.hpp @@ -17,9 +17,10 @@ struct Block { bool indestructible; BonusType bonus; bool hasBonus; + bool accelerateBall; SDL_Color color; - Block(float x, float y, float w, float h, int hp, bool indestruct, BonusType bonusType); + Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, BonusType bonusType); void Hit(); bool IsDestroyed() const; }; \ No newline at end of file diff --git a/lab1/src/Block.cpp b/lab1/src/Block.cpp index 17a1c2d..32bd375 100644 --- a/lab1/src/Block.cpp +++ b/lab1/src/Block.cpp @@ -1,17 +1,18 @@ #include "Block.hpp" -Block::Block(float x, float y, float w, float h, int hp, bool indestruct, BonusType bonusType) : +Block::Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, BonusType bonusType) : rect{x, y, w, h}, health(hp), indestructible(indestruct), + accelerateBall(accelerateBall), bonus(bonusType), hasBonus(bonusType != BonusType::None), color{128, 128, 128, 255} {} void Block::Hit() { - if(!indestructible) health--; + if (!indestructible) health--; } bool Block::IsDestroyed() const { return health <= 0 && !indestructible; -} \ No newline at end of file +} diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 39f56fd..9d49763 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -15,6 +15,9 @@ constexpr int LIVES = 1; constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s + +constexpr float ACCELERATION_BLOCK_MULTIPLIER = 1.2f; + Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f) {} @@ -60,6 +63,7 @@ void Game::ResetGame() { float y = i * (blockHeight + spacing) + 50; BonusType bonus = BonusType::None; bool indestruct = (i == 0); + bool accelerate = (i == 2); int health = indestruct ? 1 : (rows - i); if (!indestruct && (gen() % 4) == 0) { @@ -68,7 +72,7 @@ void Game::ResetGame() { blocks.emplace_back(std::make_unique( x, y, blockWidth, blockHeight, - health, indestruct, bonus + health, indestruct, accelerate, bonus )); } } @@ -134,6 +138,9 @@ void Game::CheckCollisions() { ball.y += (overlapTop < overlapBottom) ? -intersect.h : intersect.h; } + HandleBlockHit(**it); + + // Обработка удара по блоку if (!(*it)->indestructible) { (*it)->health--; @@ -174,8 +181,8 @@ void Game::HandleBlockHit(Block& block) { } // Изменение скорости мяча для специальных блоков - if (block.health == 3) { - ballSpeedMultiplier *= 1.2f; + if (block.accelerateBall) { + ballSpeedMultiplier *= ACCELERATION_BLOCK_MULTIPLIER; } } @@ -390,19 +397,23 @@ void Game::Render() { SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderRect(renderer, &block->rect); } + if (block->accelerateBall) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + SDL_RenderRect(renderer, &block->rect); + } } // Отрисовка бонусов for (const auto& bonus : activeBonuses) { SDL_Color bonusColor; switch (bonus->type) { - case BonusType::ExpandPaddle: bonusColor = {0, 255, 0, 255}; + case BonusType::ExpandPaddle: bonusColor = {0, 255, 255, 255}; break; case BonusType::ShrinkPaddle: bonusColor = {255, 0, 0, 255}; break; case BonusType::SpeedUp: bonusColor = {255, 255, 0, 255}; break; - case BonusType::StickyPaddle: bonusColor = {0, 255, 255, 255}; + case BonusType::StickyPaddle: bonusColor = {0, 255, 0, 255}; break; case BonusType::SafetyNet: bonusColor = {255, 165, 0, 255}; break; @@ -417,7 +428,7 @@ void Game::Render() { // Отрисовка HUD SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - // SDL_FRect bottomLine = {0, SCREEN_HEIGHT - 2, SCREEN_WIDTH, 2}; + if (safetyNetActive) { SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_RenderLine(renderer, 0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, SCREEN_HEIGHT - 20); @@ -443,8 +454,9 @@ void Game::ProcessInput() { else if (ballSticked && event.key.key == SDLK_SPACE) { ballSticked = false; ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; - } else if (event.key.key == SDLK_ESCAPE) { - isRunning=false; + } + else if (event.key.key == SDLK_ESCAPE) { + isRunning = false; } break; From fc0a5156c9f982524707215600b55c7cd3d231a2 Mon Sep 17 00:00:00 2001 From: Fromant Date: Thu, 29 May 2025 14:32:51 +0300 Subject: [PATCH 11/11] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20C++=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lab1/CMakeLists.txt | 8 +- lab1/README.md | 2 +- lab1/include/Ball.hpp | 112 +++++ lab1/include/Block.hpp | 40 +- lab1/include/Bonus.hpp | 14 - lab1/include/Game.hpp | 30 +- lab1/include/GameState.hpp | 39 ++ lab1/include/ICollidable.hpp | 26 ++ lab1/include/IExpirable.hpp | 19 + lab1/include/IRenderable.hpp | 7 + lab1/include/Paddle.hpp | 52 +++ lab1/include/bonuses/AccelerateBallBonus.hpp | 29 ++ lab1/include/bonuses/Bonus.hpp | 44 ++ lab1/include/bonuses/ExpandPaddleBonus.hpp | 30 ++ .../bonuses/RandomBallDirectionBonus.hpp | 16 + lab1/include/bonuses/SafetyNetBonus.hpp | 27 ++ lab1/include/bonuses/ShrinkPaddleBonus.hpp | 29 ++ lab1/include/bonuses/StickyPaddleBonus.hpp | 23 + lab1/src/Block.cpp | 17 +- lab1/src/Bonus.cpp | 13 - lab1/src/Game.cpp | 427 ++++-------------- lab1/src/bonuses/AccelerateBallBonus.cpp | 20 + lab1/src/bonuses/Bonus.cpp | 11 + lab1/src/bonuses/ExpandPaddleBonus.cpp | 19 + lab1/src/bonuses/RandomBallDirectionBonus.cpp | 26 ++ lab1/src/bonuses/SafetyNetBonus.cpp | 19 + lab1/src/bonuses/ShrinkPaddleBonus.cpp | 20 + lab1/src/bonuses/StickyPaddleBonus.cpp | 27 ++ 28 files changed, 737 insertions(+), 409 deletions(-) create mode 100644 lab1/include/Ball.hpp delete mode 100644 lab1/include/Bonus.hpp create mode 100644 lab1/include/GameState.hpp create mode 100644 lab1/include/ICollidable.hpp create mode 100644 lab1/include/IExpirable.hpp create mode 100644 lab1/include/IRenderable.hpp create mode 100644 lab1/include/Paddle.hpp create mode 100644 lab1/include/bonuses/AccelerateBallBonus.hpp create mode 100644 lab1/include/bonuses/Bonus.hpp create mode 100644 lab1/include/bonuses/ExpandPaddleBonus.hpp create mode 100644 lab1/include/bonuses/RandomBallDirectionBonus.hpp create mode 100644 lab1/include/bonuses/SafetyNetBonus.hpp create mode 100644 lab1/include/bonuses/ShrinkPaddleBonus.hpp create mode 100644 lab1/include/bonuses/StickyPaddleBonus.hpp delete mode 100644 lab1/src/Bonus.cpp create mode 100644 lab1/src/bonuses/AccelerateBallBonus.cpp create mode 100644 lab1/src/bonuses/Bonus.cpp create mode 100644 lab1/src/bonuses/ExpandPaddleBonus.cpp create mode 100644 lab1/src/bonuses/RandomBallDirectionBonus.cpp create mode 100644 lab1/src/bonuses/SafetyNetBonus.cpp create mode 100644 lab1/src/bonuses/ShrinkPaddleBonus.cpp create mode 100644 lab1/src/bonuses/StickyPaddleBonus.cpp diff --git a/lab1/CMakeLists.txt b/lab1/CMakeLists.txt index 230d45c..d380580 100644 --- a/lab1/CMakeLists.txt +++ b/lab1/CMakeLists.txt @@ -16,7 +16,13 @@ add_executable(arkanoid src/main.cpp src/Game.cpp src/Block.cpp - src/Bonus.cpp + src/bonuses/Bonus.cpp + src/bonuses/AccelerateBallBonus.cpp + src/bonuses/ExpandPaddleBonus.cpp + src/bonuses/RandomBallDirectionBonus.cpp + src/bonuses/SafetyNetBonus.cpp + src/bonuses/ShrinkPaddleBonus.cpp + src/bonuses/StickyPaddleBonus.cpp ) target_include_directories(arkanoid PRIVATE include) diff --git a/lab1/README.md b/lab1/README.md index 41372d5..5f177b4 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -21,7 +21,7 @@ cmake --build . 3. R — рестарт игры 4. SPACE или нажатие любой кнопки мыши - отпустить прилипший к каретке мяч (он изначально прилипший) 5. ESCAPE - выйти из игры -6. + ## Эффекты бонусов: - Увеличение/уменьшение каретки и скорости мяча (мгновенное) (увеличение - голубой цвет бонуса, уменьшение - красный) - Ускорение мяча (мгновенное) (жёлтый цвет) diff --git a/lab1/include/Ball.hpp b/lab1/include/Ball.hpp new file mode 100644 index 0000000..2d2021b --- /dev/null +++ b/lab1/include/Ball.hpp @@ -0,0 +1,112 @@ +#pragma once +#include +#include +#include + +#include "ICollidable.hpp" +#include "IRenderable.hpp" +#include "Paddle.hpp" +#include "SDL3/SDL_pixels.h" +#include "SDL3/SDL_render.h" + +struct GameState; + +class Ball : public IRenderable, public ICollidable { + SDL_FRect rect; + constexpr static SDL_Color COLOR{255, 255, 255, 255}; + +public: + Ball(float x, float y, float w, float h) : rect{x, y, w, h} {} + + void render(SDL_Renderer* renderer) override { + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); + } + + SDL_FRect& getCollisionRect() override { + return rect; + } + + bool checkCollisionWith(ICollidable& other, GameState& state) override { + bool r = ICollidable::checkCollisionWith(other, state); + if (!r) return false; + if (dynamic_cast(&other)) { + if (!state.stickyPaddle) { + float hitPosition = (rect.x + rect.w / 2 - other.getCollisionRect().x) / other.getCollisionRect().w - + 0.5f; + float prevVelocity = sqrtf( + state.ballVelocity.x * state.ballVelocity.x + state.ballVelocity.y * state.ballVelocity.y); + state.ballVelocity.x = hitPosition * prevVelocity; + state.ballVelocity.y = - + sqrtf(prevVelocity * prevVelocity - state.ballVelocity.x * state.ballVelocity.x); + } + else { + state.ballSticked = true; + state.stickyPaddle = false; + } + } + return true; + } + + void hit(GameState& gameState, SDL_FRect& collisionResult) override { + float overlapLeft = rect.x + rect.w - collisionResult.x; + float overlapRight = collisionResult.x + collisionResult.w - rect.x; + float overlapTop = rect.y + rect.h - collisionResult.y; + float overlapBottom = collisionResult.y + collisionResult.h - rect.y; + + bool horizontalCollision = std::min(overlapLeft, overlapRight) < + std::min(overlapTop, overlapBottom); + + if (horizontalCollision) { + gameState.ballVelocity.x = (overlapLeft < overlapRight) + ? -std::fabs(gameState.ballVelocity.x) + : fabs(gameState.ballVelocity.x); + rect.x += (overlapLeft < overlapRight) ? -collisionResult.w : collisionResult.w; + } + else { + gameState.ballVelocity.y = (overlapTop < overlapBottom) + ? -fabs(gameState.ballVelocity.y) + : fabs(gameState.ballVelocity.y); + rect.y += (overlapTop < overlapBottom) ? -collisionResult.h : collisionResult.h; + } + } + + bool update(float deltaTime, GameState& state, Paddle& paddle) { + if (state.ballSticked) { + rect.x = paddle.getCollisionRect().x + paddle.getCollisionRect().w / 2 - rect.w / 2; + rect.y = paddle.getCollisionRect().y - rect.h; + return false; + } + + rect.x += state.ballVelocity.x * deltaTime * state.ballSpeedMultiplier; + rect.y += state.ballVelocity.y * deltaTime * state.ballSpeedMultiplier; + + // Проверка поражения + if (rect.y + rect.h > SCREEN_HEIGHT) { + if (state.safetyNetActive) { + // Отскок от защитной сетки + state.ballVelocity.y = -fabs(state.ballVelocity.y); + rect.y = SCREEN_HEIGHT - 2 * BASE_BALL_HEIGHT; + state.safetyNetActive = false; + } + else { + state.lives--; + if (state.lives > 0) { + // Респавн мяча + rect = { + SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_HEIGHT / 2, + BASE_BALL_WIDTH, BASE_BALL_HEIGHT + }; + state.ballVelocity = {0, -BASE_BALL_SPEED}; + state.ballSticked = true; + } + else { + return true; + } + } + } + return false; + } + + ~Ball() override = default; +}; diff --git a/lab1/include/Block.hpp b/lab1/include/Block.hpp index 2ab4163..18dd31f 100644 --- a/lab1/include/Block.hpp +++ b/lab1/include/Block.hpp @@ -1,26 +1,36 @@ #pragma once #include +#include "GameState.hpp" +#include "bonuses/Bonus.hpp" -enum class BonusType { - None, - ExpandPaddle, - ShrinkPaddle, - SpeedUp, - StickyPaddle, - SafetyNet, - RandomDirection -}; +constexpr float ACCELERATION_BLOCK_MULTIPLIER = 1.05f; -struct Block { +struct Block : ICollidable, IRenderable { SDL_FRect rect; int health; bool indestructible; - BonusType bonus; - bool hasBonus; bool accelerateBall; SDL_Color color; + Bonus* innerBonus; - Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, BonusType bonusType); - void Hit(); + Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, Bonus* bonus); bool IsDestroyed() const; -}; \ No newline at end of file + + void render(SDL_Renderer* renderer) override { + SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); + SDL_RenderFillRect(renderer, &rect); + if (indestructible) { + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); + SDL_RenderRect(renderer, &rect); + } + if (accelerateBall) { + SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); + SDL_RenderRect(renderer, &rect); + } + } + + SDL_FRect& getCollisionRect() override { return rect; }; + void hit(GameState& gameState, SDL_FRect& collisionResult) override; + + ~Block() override = default; +}; diff --git a/lab1/include/Bonus.hpp b/lab1/include/Bonus.hpp deleted file mode 100644 index 4a582e8..0000000 --- a/lab1/include/Bonus.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#include -#include "Block.hpp" - -struct Bonus { - SDL_FRect rect; - BonusType type; - bool active; - float fallSpeed; - - Bonus(float x, float y, BonusType t); - void Update(float deltaTime); - void Activate(class Game& game); -}; \ No newline at end of file diff --git a/lab1/include/Game.hpp b/lab1/include/Game.hpp index 060efcc..ce9e190 100644 --- a/lab1/include/Game.hpp +++ b/lab1/include/Game.hpp @@ -2,8 +2,11 @@ #include #include #include + +#include "Ball.hpp" #include "Block.hpp" -#include "Bonus.hpp" +#include "Paddle.hpp" +#include "bonuses/Bonus.hpp" class Game { public: @@ -12,7 +15,6 @@ class Game { bool Initialize(); void Run(); - void ActivateBonus(BonusType type); void ResetGame(); private: @@ -23,7 +25,7 @@ class Game { void Render(); void CheckCollisions(); void HandleBallBlockCollision(Block& block); - void SpawnBonus(float x, float y, BonusType type); + void SpawnBonus(float x, float y); void HandleBlockHit(Block& block); SDL_Window* window; @@ -32,23 +34,13 @@ class Game { bool needsReset; // Game objects - SDL_FRect paddle; - SDL_FRect ball; - SDL_FPoint ballVelocity; + Paddle paddle; + Ball ball; std::vector> blocks; std::vector> activeBonuses; - // Game state - int score; - int lives; - bool stickyPaddle; - bool safetyNetActive; - bool ballSticked = false; - float paddleSpeedMultiplier; - float ballSpeedMultiplier; - const float maxBallSpeed = 500; // Максимальная скорость мяча - const float ballRadius = ball.w / 2.0f; // Радиус для точных расчетов - Uint64 safetyNetExpireTime = 0; - Uint64 stickyPaddleExpireTime = 0; -}; \ No newline at end of file + + GameState state{}; + static constexpr GameState DEFAULT_STATE{}; +}; diff --git a/lab1/include/GameState.hpp b/lab1/include/GameState.hpp new file mode 100644 index 0000000..98b69ff --- /dev/null +++ b/lab1/include/GameState.hpp @@ -0,0 +1,39 @@ +#pragma once + +constexpr int LIVES = 1; +constexpr int SCREEN_WIDTH = 800; +constexpr int SCREEN_HEIGHT = 600; +constexpr float BASE_BALL_SPEED = 300.0f; +constexpr int BASE_PADDLE_WIDTH = 100; +constexpr int BASE_PADDLE_HEIGHT = 20; +constexpr float BASE_BALL_HEIGHT = 20; +constexpr float BASE_BALL_WIDTH = 20; + +struct GameState { + GameState() = default; + + //copy assignment + GameState& operator=(GameState const& other) { + score = other.score; + lives = other.lives; + stickyPaddle = other.stickyPaddle; + safetyNetActive = other.safetyNetActive; + ballSticked = other.ballSticked; + paddleSpeedMultiplier = other.paddleSpeedMultiplier; + ballSpeedMultiplier = other.ballSpeedMultiplier; + paddleWidthMultiplier = other.paddleWidthMultiplier; + ballVelocity = other.ballVelocity; + return *this; + } + + int score = 0; + int lives = LIVES; + bool stickyPaddle = false; + bool safetyNetActive = true; + bool ballSticked = true; + float paddleSpeedMultiplier = 1.0f; + float ballSpeedMultiplier = 1.0f; + float paddleWidthMultiplier = 1.0f; + SDL_FPoint ballVelocity{0, -BASE_BALL_SPEED}; + const float maxBallSpeed = 500.0f; // Максимальная скорость мяча +}; diff --git a/lab1/include/ICollidable.hpp b/lab1/include/ICollidable.hpp new file mode 100644 index 0000000..941bbd9 --- /dev/null +++ b/lab1/include/ICollidable.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "SDL3/SDL_rect.h" + +struct GameState; + +class ICollidable { +protected: + virtual void hit(GameState& gameState, SDL_FRect& collisionResult) = 0; + +public: + virtual SDL_FRect& getCollisionRect() = 0; + + virtual bool checkCollisionWith(ICollidable& other, GameState& state) { + SDL_FRect intersection; + if (SDL_GetRectIntersectionFloat(&other.getCollisionRect(), &getCollisionRect(), &intersection)) { + //collision + hit(state, intersection); + other.hit(state, intersection); + return true; + } + return false; + } + + virtual ~ICollidable() = default; +}; diff --git a/lab1/include/IExpirable.hpp b/lab1/include/IExpirable.hpp new file mode 100644 index 0000000..4719bc0 --- /dev/null +++ b/lab1/include/IExpirable.hpp @@ -0,0 +1,19 @@ +#pragma once +class IExpirable { +public: + virtual ~IExpirable() = default; + + bool activated = false; + + IExpirable(float duration) : duration(duration) {} + + virtual void update(const float dt, GameState& state) { + duration -= dt; + if (duration < 0) expire(state); + } + +protected: + float duration; // in seconds + + virtual void expire(GameState& state) = 0; +}; diff --git a/lab1/include/IRenderable.hpp b/lab1/include/IRenderable.hpp new file mode 100644 index 0000000..b8d8245 --- /dev/null +++ b/lab1/include/IRenderable.hpp @@ -0,0 +1,7 @@ +#pragma once + +class IRenderable { +public: + virtual void render(SDL_Renderer* renderer) = 0; + virtual ~IRenderable() = default; +}; diff --git a/lab1/include/Paddle.hpp b/lab1/include/Paddle.hpp new file mode 100644 index 0000000..79dacee --- /dev/null +++ b/lab1/include/Paddle.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "GameState.hpp" +#include "bonuses/Bonus.hpp" +#include "SDL3/SDL_pixels.h" +#include "SDL3/SDL_render.h" + +constexpr float BASE_PADDLE_SPEED = 200.0f; + +class Paddle : public ICollidable, public IRenderable { + SDL_FRect rect; + constexpr static SDL_Color COLOR{255, 255, 255, 255}; + constexpr static SDL_Color STICKY_COLOR{255, 255, 255, 255}; + +public: + Paddle(float x, float y, float w, float h) : rect{x, y, w, h} {} + + void render(SDL_Renderer* renderer) override { + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); + } + + SDL_FRect& getCollisionRect() override { + return rect; + } + + void update(float deltaTime, GameState& state) { + // Клавиатурный ввод для движения каретки + const bool* keyboardState = SDL_GetKeyboardState(nullptr); + float moveSpeed = BASE_PADDLE_SPEED * state.paddleSpeedMultiplier * deltaTime; + + if (keyboardState[SDL_SCANCODE_LEFT]) { + rect.x -= moveSpeed; + } + if (keyboardState[SDL_SCANCODE_RIGHT]) { + rect.x += moveSpeed; + } + + rect.w = state.paddleWidthMultiplier * BASE_PADDLE_WIDTH; + + + // Ограничение движения каретки + if (rect.x < 0) rect.x = 0; + if (rect.x > SCREEN_WIDTH - rect.w) { + rect.x = SCREEN_WIDTH - rect.w; + } + } + + void hit(GameState& gameState, SDL_FRect& collisionResult) override {} + + ~Paddle() override = default; +}; diff --git a/lab1/include/bonuses/AccelerateBallBonus.hpp b/lab1/include/bonuses/AccelerateBallBonus.hpp new file mode 100644 index 0000000..1858854 --- /dev/null +++ b/lab1/include/bonuses/AccelerateBallBonus.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Bonus.hpp" + +#include "IExpirable.hpp" + +class AccelerateBallBonus : Bonus, IExpirable { + static constexpr SDL_Color COLOR{255, 255, 0, 255}; + static constexpr float ACCELERATE_BALL_MULTIPLIER = 1.2f; + static constexpr float DURATION = 5; //5s +public: + AccelerateBallBonus(int x, int y) : Bonus(x, y), IExpirable(DURATION) {} + + void render(SDL_Renderer* renderer) override; + + void update(float deltaTime, GameState& state) override { + Bonus::update(deltaTime, state); + if (activated) IExpirable::update(deltaTime, state); + } + + void expire(GameState& state) override { + deactivate(state); + } + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + + ~AccelerateBallBonus() override = default; +}; diff --git a/lab1/include/bonuses/Bonus.hpp b/lab1/include/bonuses/Bonus.hpp new file mode 100644 index 0000000..7ac1cd6 --- /dev/null +++ b/lab1/include/bonuses/Bonus.hpp @@ -0,0 +1,44 @@ +#pragma once +#include + +#include "GameState.hpp" +#include "IRenderable.hpp" +#include "ICollidable.hpp" + +constexpr float fallSpeed = 100; //pixels/sec +constexpr int BONUS_WIDTH = 40; +constexpr int BONUS_HEIGHT = 40; + +class Bonus : public IRenderable, public ICollidable { +protected: + SDL_FRect rect; + bool active = true; + +public: + Bonus(float x, float y); + bool doRender = false; + + SDL_FRect& getCollisionRect() override { return rect; } + + void hit(GameState& game, SDL_FRect& collisionResult) override { + activate(game); + disableRendering(); + } + + virtual void enableRendering() { + doRender = true; + } + + virtual void disableRendering() { + doRender = false; + } + + virtual bool isActive() const { return active; } + + void render(SDL_Renderer* renderer) override = 0; + virtual void update(float deltaTime, GameState& state); + virtual void activate(GameState& game) = 0; + virtual void deactivate(GameState& game) = 0; + + ~Bonus() override = default; +}; diff --git a/lab1/include/bonuses/ExpandPaddleBonus.hpp b/lab1/include/bonuses/ExpandPaddleBonus.hpp new file mode 100644 index 0000000..e3b6c1c --- /dev/null +++ b/lab1/include/bonuses/ExpandPaddleBonus.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "Bonus.hpp" +#include "IExpirable.hpp" + +class ExpandPaddleBonus : Bonus, IExpirable { + static constexpr SDL_Color COLOR{0, 255, 255, 255}; + static constexpr float EXPAND_PADDLE_MULTIPLIER = 1.2f; + static constexpr float DURATION = 5.0f; //5s + + bool activated = false; + +public: + ExpandPaddleBonus(int x, int y) : Bonus(x, y), IExpirable(DURATION) {} + + void render(SDL_Renderer* renderer) override; + + void update(float deltaTime, GameState& state) override { + Bonus::update(deltaTime, state); + if (activated) IExpirable::update(deltaTime, state); + } + + void expire(GameState& state) override { + deactivate(state); + } + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + ~ExpandPaddleBonus() override = default; +}; diff --git a/lab1/include/bonuses/RandomBallDirectionBonus.hpp b/lab1/include/bonuses/RandomBallDirectionBonus.hpp new file mode 100644 index 0000000..3e2576b --- /dev/null +++ b/lab1/include/bonuses/RandomBallDirectionBonus.hpp @@ -0,0 +1,16 @@ +#pragma once +#include "Bonus.hpp" + +class RandomBallDirectionBonus : Bonus { + static constexpr SDL_Color COLOR{255, 0, 255, 255}; + +public: + RandomBallDirectionBonus(int x, int y) : Bonus(x,y) {} + + void render(SDL_Renderer* renderer) override; + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + ~RandomBallDirectionBonus() override = default; +}; diff --git a/lab1/include/bonuses/SafetyNetBonus.hpp b/lab1/include/bonuses/SafetyNetBonus.hpp new file mode 100644 index 0000000..dda8abe --- /dev/null +++ b/lab1/include/bonuses/SafetyNetBonus.hpp @@ -0,0 +1,27 @@ +#pragma once +#include "Bonus.hpp" +#include "IExpirable.hpp" + +class SafetyNetBonus : Bonus, IExpirable { + static constexpr SDL_Color COLOR{255, 165, 0, 255}; + static constexpr float DURATION = 3.0f; //3s + +public: + SafetyNetBonus(int x, int y) : Bonus(x, y), IExpirable(DURATION) {} + + void render(SDL_Renderer* renderer) override; + + void update(float deltaTime, GameState& state) override { + Bonus::update(deltaTime, state); + if (activated) IExpirable::update(deltaTime, state); + } + + void expire(GameState& state) override { + deactivate(state); + } + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + ~SafetyNetBonus() override = default; +}; diff --git a/lab1/include/bonuses/ShrinkPaddleBonus.hpp b/lab1/include/bonuses/ShrinkPaddleBonus.hpp new file mode 100644 index 0000000..2b89e56 --- /dev/null +++ b/lab1/include/bonuses/ShrinkPaddleBonus.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "Bonus.hpp" +#include "IExpirable.hpp" + +class ShrinkPaddleBonus : Bonus, IExpirable { + static constexpr SDL_Color COLOR{255, 0, 0, 255}; + static constexpr float SHRINK_PADDLE_MULTIPLIER = 1.2f; + static constexpr float DURATION = 5.0f; //5s + bool activated = false; + +public: + ShrinkPaddleBonus(int x, int y) : Bonus(x, y), IExpirable(DURATION) {} + + void render(SDL_Renderer* renderer) override; + + void update(float deltaTime, GameState& state) override { + Bonus::update(deltaTime, state); + if (activated) IExpirable::update(deltaTime, state); + } + + void expire(GameState& state) override { + deactivate(state); + } + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + ~ShrinkPaddleBonus() override = default; +}; diff --git a/lab1/include/bonuses/StickyPaddleBonus.hpp b/lab1/include/bonuses/StickyPaddleBonus.hpp new file mode 100644 index 0000000..db4f9f8 --- /dev/null +++ b/lab1/include/bonuses/StickyPaddleBonus.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include "Bonus.hpp" +#include "IExpirable.hpp" + +class StickyPaddleBonus : Bonus, IExpirable { + static constexpr SDL_Color COLOR{0, 255, 0, 255}; + static constexpr float DURATION = 4.0f; //4s + +public: + StickyPaddleBonus(int x, int y) : Bonus(x, y), IExpirable(DURATION) {} + + void render(SDL_Renderer* renderer) override; + + void update(float deltaTime, GameState& state) override; + + void expire(GameState& state) override; + + void activate(GameState& game) override; + void deactivate(GameState& game) override; + + ~StickyPaddleBonus() override = default; +}; diff --git a/lab1/src/Block.cpp b/lab1/src/Block.cpp index 32bd375..e5a96e5 100644 --- a/lab1/src/Block.cpp +++ b/lab1/src/Block.cpp @@ -1,16 +1,21 @@ #include "Block.hpp" -Block::Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, BonusType bonusType) : +Block::Block(float x, float y, float w, float h, int hp, bool indestruct, bool accelerateBall, Bonus* bonus) : rect{x, y, w, h}, health(hp), indestructible(indestruct), accelerateBall(accelerateBall), - bonus(bonusType), - hasBonus(bonusType != BonusType::None), - color{128, 128, 128, 255} {} + color{128, 128, 128, 255}, + innerBonus(bonus) {} -void Block::Hit() { - if (!indestructible) health--; +void Block::hit(GameState& gameState, SDL_FRect& collisionResult) { + if (!indestructible) { + health--; + gameState.score++; + } + if (accelerateBall) { + gameState.ballSpeedMultiplier *= ACCELERATION_BLOCK_MULTIPLIER; + } } bool Block::IsDestroyed() const { diff --git a/lab1/src/Bonus.cpp b/lab1/src/Bonus.cpp deleted file mode 100644 index 9f49771..0000000 --- a/lab1/src/Bonus.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "Bonus.hpp" -#include "Game.hpp" - -Bonus::Bonus(float x, float y, BonusType t) : - rect{x, y, 30, 15}, type(t), active(true), fallSpeed(0.2f) {} - -void Bonus::Update(float deltaTime) { - rect.y += fallSpeed * deltaTime; -} - -void Bonus::Activate(Game& game) { - game.ActivateBonus(type); -} \ No newline at end of file diff --git a/lab1/src/Game.cpp b/lab1/src/Game.cpp index 9d49763..fe5866d 100644 --- a/lab1/src/Game.cpp +++ b/lab1/src/Game.cpp @@ -1,40 +1,28 @@ #include "Game.hpp" #include #include +#include #include -constexpr int SCREEN_WIDTH = 800; -constexpr int SCREEN_HEIGHT = 600; -constexpr float BASE_PADDLE_SPEED = 200.0f; -constexpr int BASE_PADDLE_WIDTH = 100; -constexpr int BASE_PADDLE_HEIGHT = 20; -constexpr float BASE_BALL_SPEED = 300.0f; -constexpr float BASE_BALL_HEIGHT = 20; -constexpr float BASE_BALL_WIDTH = 20; -constexpr int LIVES = 1; -constexpr Uint64 STICKY_PADDLE_DURATION = 10000; //10s -constexpr Uint64 SAFETY_NET_DURATION = 10000; //10s - - -constexpr float ACCELERATION_BLOCK_MULTIPLIER = 1.2f; +#include "bonuses/AccelerateBallBonus.hpp" +#include "bonuses/ExpandPaddleBonus.hpp" +#include "bonuses/RandomBallDirectionBonus.hpp" +#include "bonuses/SafetyNetBonus.hpp" +#include "bonuses/ShrinkPaddleBonus.hpp" +#include "bonuses/StickyPaddleBonus.hpp" Game::Game() : window(nullptr), renderer(nullptr), isRunning(true), needsReset(false), - score(0), lives(LIVES), stickyPaddle(false), safetyNetActive(false), - paddleSpeedMultiplier(1.0f), ballSpeedMultiplier(1.0f) {} + ball{ + SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_WIDTH / 2, BASE_BALL_WIDTH, + BASE_BALL_HEIGHT + }, paddle{ + SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, BASE_PADDLE_WIDTH, + BASE_PADDLE_HEIGHT + } {} void Game::ResetGame() { // Reset game state - lives = LIVES; - score = 0; - stickyPaddle = false; - ballSticked = true; - safetyNetActive = false; - paddleSpeedMultiplier = 1.0f; - ballSpeedMultiplier = 1.0f; - safetyNetExpireTime = 0; - stickyPaddleExpireTime = 0; - - // Reset paddle and ball + state = DEFAULT_STATE; paddle = { SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, BASE_PADDLE_WIDTH, BASE_PADDLE_HEIGHT @@ -43,8 +31,6 @@ void Game::ResetGame() { SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_WIDTH / 2, BASE_BALL_WIDTH, BASE_BALL_HEIGHT }; - ballVelocity = {0, -BASE_BALL_SPEED}; - // Rebuild blocks blocks.clear(); const int rows = 4; @@ -53,21 +39,30 @@ void Game::ResetGame() { const float blockHeight = 20; const float spacing = 5; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> bonusDist(1, 6); + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution<> bonusDist(0, 5); + + const static std::array, 6> bonusFactories = { + [](int x, int y) { return (Bonus*)new AccelerateBallBonus(x, y); }, + [](int x, int y) { return (Bonus*)new ExpandPaddleBonus(x, y); }, + [](int x, int y) { return (Bonus*)new RandomBallDirectionBonus(x, y); }, + [](int x, int y) { return (Bonus*)new SafetyNetBonus(x, y); }, + [](int x, int y) { return (Bonus*)new ShrinkPaddleBonus(x, y); }, + [](int x, int y) { return (Bonus*)new StickyPaddleBonus(x, y); } + }; for (int i = 0; i < rows; ++i) { for (int j = 0; j < cols; ++j) { float x = j * (blockWidth + spacing) + spacing; float y = i * (blockHeight + spacing) + 50; - BonusType bonus = BonusType::None; bool indestruct = (i == 0); bool accelerate = (i == 2); int health = indestruct ? 1 : (rows - i); + Bonus* bonus = nullptr; if (!indestruct && (gen() % 4) == 0) { - bonus = static_cast(bonusDist(gen)); + bonus = bonusFactories[bonusDist(gen)](x, y); } blocks.emplace_back(std::make_unique( @@ -82,183 +77,47 @@ void Game::ResetGame() { void Game::CheckCollisions() { // Обработка границ экрана - if (ball.x < 0) { - ball.x = 0; - ballVelocity.x = fabs(ballVelocity.x); + if (ball.getCollisionRect().x < 0) { + ball.getCollisionRect().x = 0; + state.ballVelocity.x = fabs(state.ballVelocity.x); } - if (ball.x + ball.w > SCREEN_WIDTH) { - ball.x = SCREEN_WIDTH - ball.w; - ballVelocity.x = -fabs(ballVelocity.x); + if (ball.getCollisionRect().x + ball.getCollisionRect().w > SCREEN_WIDTH) { + ball.getCollisionRect().x = SCREEN_WIDTH - ball.getCollisionRect().w; + state.ballVelocity.x = -fabs(state.ballVelocity.x); } - if (ball.y < 0) { - ball.y = 0; - ballVelocity.y = fabs(ballVelocity.y); + if (ball.getCollisionRect().y < 0) { + ball.getCollisionRect().y = 0; + state.ballVelocity.y = fabs(state.ballVelocity.y); } - // Коллизия с кареткой - SDL_FRect intersection; - if (SDL_GetRectIntersectionFloat(&ball, &paddle, &intersection)) { - // Корректировка позиции мяча - ball.y = paddle.y - ball.h; - - if (!stickyPaddle) { + if (ball.checkCollisionWith(paddle, state)) { + //collision + if (!state.stickyPaddle) { // Расчет точки удара [-0.5, 0.5] - float hitPosition = (ball.x + ball.w / 2 - paddle.x) / paddle.w - 0.5f; - float prevVelocity = sqrtf(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y); - ballVelocity.x = hitPosition * prevVelocity; - ballVelocity.y = -sqrtf(prevVelocity * prevVelocity - ballVelocity.x * ballVelocity.x); + float hitPosition = (ball.getCollisionRect().x + ball.getCollisionRect().w / 2 - paddle.getCollisionRect(). + x) / paddle.getCollisionRect().w - 0.5f; + float prevVelocity = sqrtf( + state.ballVelocity.x * state.ballVelocity.x + state.ballVelocity.y * state.ballVelocity.y); + state.ballVelocity.x = hitPosition * prevVelocity; + state.ballVelocity.y = -sqrtf(prevVelocity * prevVelocity - state.ballVelocity.x * state.ballVelocity.x); } else { - ballSticked = true; - stickyPaddle = false; + state.ballSticked = true; + state.stickyPaddle = false; } } - // Коллизия с блоками - for (auto it = blocks.begin(); it != blocks.end();) { - SDL_FRect blockRect = (*it)->rect; - SDL_FRect intersect; - - if (SDL_GetRectIntersectionFloat(&ball, &blockRect, &intersect)) { - // Определение направления отскока - float overlapLeft = ball.x + ball.w - blockRect.x; - float overlapRight = blockRect.x + blockRect.w - ball.x; - float overlapTop = ball.y + ball.h - blockRect.y; - float overlapBottom = blockRect.y + blockRect.h - ball.y; - - bool horizontalCollision = std::min(overlapLeft, overlapRight) < - std::min(overlapTop, overlapBottom); - - if (horizontalCollision) { - ballVelocity.x = (overlapLeft < overlapRight) ? -fabs(ballVelocity.x) : fabs(ballVelocity.x); - ball.x += (overlapLeft < overlapRight) ? -intersect.w : intersect.w; - } - else { - ballVelocity.y = (overlapTop < overlapBottom) ? -fabs(ballVelocity.y) : fabs(ballVelocity.y); - ball.y += (overlapTop < overlapBottom) ? -intersect.h : intersect.h; - } - - HandleBlockHit(**it); - - - // Обработка удара по блоку - if (!(*it)->indestructible) { - (*it)->health--; - score++; - - if ((*it)->health <= 0) { - if ((*it)->hasBonus) { - SpawnBonus(blockRect.x + blockRect.w / 2, - blockRect.y + blockRect.h / 2, - (*it)->bonus); - } - it = blocks.erase(it); - continue; - } - } - } - ++it; - } - // Коллизия бонусов с кареткой - for (auto it = activeBonuses.begin(); it != activeBonuses.end();) { - SDL_FRect bonusRect = (*it)->rect; - if (SDL_GetRectIntersectionFloat(&bonusRect, &paddle, &intersection)) { - ActivateBonus((*it)->type); - it = activeBonuses.erase(it); - } - else { - if (bonusRect.y > SCREEN_HEIGHT) it = activeBonuses.erase(it); - else ++it; - } - } -} - -void Game::HandleBlockHit(Block& block) { - if (!block.indestructible) { - block.Hit(); - score += 1; // +1 очко за попадание - } - - // Изменение скорости мяча для специальных блоков - if (block.accelerateBall) { - ballSpeedMultiplier *= ACCELERATION_BLOCK_MULTIPLIER; - } -} - - -void Game::HandleBallBlockCollision(Block& block) { - block.Hit(); - - // Calculate collision normal - float ballCenterX = ball.x + ball.w / 2; - float ballCenterY = ball.y + ball.h / 2; - float blockCenterX = block.rect.x + block.rect.w / 2; - float blockCenterY = block.rect.y + block.rect.h / 2; - - float dx = ballCenterX - blockCenterX; - float dy = ballCenterY - blockCenterY; - float absDx = fabs(dx); - float absDy = fabs(dy); - - if (absDx > absDy) { - // Horizontal collision - ballVelocity.x *= -1; - } - else { - // Vertical collision - ballVelocity.y *= -1; + for (auto& bonus : activeBonuses) { + if (bonus->doRender) bonus->checkCollisionWith(paddle, state); } -} - -void Game::ActivateBonus(BonusType type) { - switch (type) { - case BonusType::ExpandPaddle: - paddle.w = BASE_PADDLE_WIDTH * 1.5f; - break; - - case BonusType::ShrinkPaddle: - paddle.w = BASE_PADDLE_WIDTH * 0.75f; - break; - - case BonusType::SpeedUp: - ballSpeedMultiplier *= 1.2f; - break; - - case BonusType::StickyPaddle: - stickyPaddle = true; - stickyPaddleExpireTime = SDL_GetTicks() + STICKY_PADDLE_DURATION; - break; - - case BonusType::SafetyNet: - safetyNetActive = true; - safetyNetExpireTime = SDL_GetTicks() + SAFETY_NET_DURATION; - break; - - case BonusType::RandomDirection: { - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution angleDist(-0.5f, 0.5f); - ballVelocity.x += angleDist(gen); - ballVelocity.y += angleDist(gen); - // Normalize speed - float speed = sqrtf(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y); - ballVelocity.x = (ballVelocity.x / speed) * BASE_BALL_SPEED * ballSpeedMultiplier; - ballVelocity.y = (ballVelocity.y / speed) * BASE_BALL_SPEED * ballSpeedMultiplier; - break; - } - default: - break; + // Коллизия блоков с мячом + for (auto& block : blocks) { + block->checkCollisionWith(ball, state); } } -void Game::SpawnBonus(float x, float y, BonusType type) { - auto bonus = std::make_unique(x, y, type); - activeBonuses.push_back(std::move(bonus)); -} - - void Game::Run() { Uint64 previousTicks = SDL_GetTicks(); @@ -268,104 +127,55 @@ void Game::Run() { previousTicks = currentTicks; ProcessInput(); + SDL_Delay(std::max(0.0f, 16 - deltaTime*1000)); + deltaTime = 0.016f; + Update(deltaTime); Render(); } } void Game::UpdatePaddle(const float deltaTime) { - // Клавиатурный ввод для движения каретки - const bool* keyboardState = SDL_GetKeyboardState(nullptr); - float moveSpeed = BASE_PADDLE_SPEED * paddleSpeedMultiplier * deltaTime; - - if (keyboardState[SDL_SCANCODE_LEFT]) { - paddle.x -= moveSpeed; - } - if (keyboardState[SDL_SCANCODE_RIGHT]) { - paddle.x += moveSpeed; - } - - // Ограничение движения каретки - if (paddle.x < 0) paddle.x = 0; - if (paddle.x > SCREEN_WIDTH - paddle.w) { - paddle.x = SCREEN_WIDTH - paddle.w; - } + paddle.update(deltaTime, state); } void Game::UpdateBonuses(const float deltaTime) { // Движение бонусов вниз for (auto& bonus : activeBonuses) { - bonus->rect.y += 150.0f * deltaTime; // Скорость падения - if (bonus->rect.y > SCREEN_HEIGHT) { - bonus->active = false; - } + bonus->update(deltaTime, state); } // Удаление неактивных бонусов activeBonuses.erase( std::remove_if(activeBonuses.begin(), activeBonuses.end(), - [](const auto& b) { return !b->active; }), + [](const auto& b) { return !b->isActive(); }), activeBonuses.end() ); - - // Обновление бонусов - for (auto& bonus : activeBonuses) { - bonus->Update(deltaTime); - } - - // Проверка времени действия бонусов - Uint64 currentTime = SDL_GetTicks(); - if (stickyPaddle && stickyPaddleExpireTime && currentTime > stickyPaddleExpireTime) { - stickyPaddle = false; - } - if (safetyNetActive && safetyNetExpireTime && currentTime > safetyNetExpireTime) { - safetyNetActive = false; - } } void Game::Update(const float deltaTime) { // Обновление позиции мяча с учётом множителя скорости - ball.x += ballVelocity.x * deltaTime * ballSpeedMultiplier; - ball.y += ballVelocity.y * deltaTime * ballSpeedMultiplier; - - // Проверка поражения - if (ball.y + ball.h > SCREEN_HEIGHT) { - if (safetyNetActive) { - // Отскок от защитной сетки - ballVelocity.y = -fabs(ballVelocity.y); - safetyNetActive = false; - } - else { - lives--; - if (lives > 0) { - // Респавн мяча - ball = { - SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_HEIGHT / 2, - BASE_BALL_WIDTH, BASE_BALL_HEIGHT - }; - ballVelocity = {0, -BASE_BALL_SPEED}; - ballSticked = true; - } - else { - ResetGame(); - } - } - } + if (ball.update(deltaTime, state, paddle)) ResetGame(); CheckCollisions(); UpdateBonuses(deltaTime); UpdatePaddle(deltaTime); - // Прилипание мяча к каретке - if (ballSticked) { - ball.x = paddle.x + paddle.w / 2 - ball.w / 2; - ball.y = paddle.y - ball.h; - } - - // Удаление разрушенных блоков + // Удаление разрушенных блоков и спавн бонусов + auto bonusesRef = std::ref(activeBonuses); blocks.erase( std::remove_if(blocks.begin(), blocks.end(), - [](const auto& block) { return block->IsDestroyed(); }), + [bonusesRef](const std::unique_ptr& block) { + if (block->IsDestroyed()) { + //spawn bonus + if (block->innerBonus) { + bonusesRef.get().emplace_back(block->innerBonus); + block->innerBonus->enableRendering(); + } + return true; + } + return false; + }), blocks.end() ); } @@ -374,62 +184,23 @@ void Game::Render() { SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); - // Отрисовка каретки - SDL_SetRenderDrawColor(renderer, stickyPaddle ? 0 : 255, 255, stickyPaddle ? 0 : 255, 255); - SDL_RenderFillRect(renderer, &paddle); - - // Отрисовка мяча - SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); - SDL_RenderFillRect(renderer, &ball); + paddle.render(renderer); + ball.render(renderer); // Отрисовка блоков for (const auto& block : blocks) { - SDL_SetRenderDrawColor(renderer, - block->color.r, - block->color.g, - block->color.b, - block->color.a - ); - SDL_RenderFillRect(renderer, &block->rect); - - // Для неразрушаемых блоков добавляем рамку - if (block->indestructible) { - SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - SDL_RenderRect(renderer, &block->rect); - } - if (block->accelerateBall) { - SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); - SDL_RenderRect(renderer, &block->rect); - } + block->render(renderer); } // Отрисовка бонусов for (const auto& bonus : activeBonuses) { - SDL_Color bonusColor; - switch (bonus->type) { - case BonusType::ExpandPaddle: bonusColor = {0, 255, 255, 255}; - break; - case BonusType::ShrinkPaddle: bonusColor = {255, 0, 0, 255}; - break; - case BonusType::SpeedUp: bonusColor = {255, 255, 0, 255}; - break; - case BonusType::StickyPaddle: bonusColor = {0, 255, 0, 255}; - break; - case BonusType::SafetyNet: bonusColor = {255, 165, 0, 255}; - break; - case BonusType::RandomDirection: bonusColor = {255, 0, 255, 255}; - break; - default: bonusColor = {255, 255, 255, 255}; - } - SDL_SetRenderDrawColor(renderer, - bonusColor.r, bonusColor.g, bonusColor.b, bonusColor.a); - SDL_RenderFillRect(renderer, &bonus->rect); + bonus->render(renderer); } // Отрисовка HUD SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); - if (safetyNetActive) { + if (state.safetyNetActive) { SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255); SDL_RenderLine(renderer, 0, SCREEN_HEIGHT - 20, SCREEN_WIDTH, SCREEN_HEIGHT - 20); } @@ -451,9 +222,10 @@ void Game::ProcessInput() { ResetGame(); } // Отпускание мяча при липкой каретке - else if (ballSticked && event.key.key == SDLK_SPACE) { - ballSticked = false; - ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; + else if (state.ballSticked && event.key.key == SDLK_SPACE + ) { + state.ballSticked = false; + state.ballVelocity = {0, -BASE_BALL_SPEED * state.ballSpeedMultiplier}; } else if (event.key.key == SDLK_ESCAPE) { isRunning = false; @@ -462,15 +234,15 @@ void Game::ProcessInput() { case SDL_EVENT_MOUSE_BUTTON_DOWN: // Отпускание мяча по клику мыши - if (ballSticked) { - ballSticked = false; - ballVelocity = {0, -BASE_BALL_SPEED * ballSpeedMultiplier}; + if (state.ballSticked) { + state.ballSticked = false; + state.ballVelocity = {0, -BASE_BALL_SPEED * state.ballSpeedMultiplier}; } break; case SDL_EVENT_MOUSE_MOTION: // Управление кареткой мышью - paddle.x = event.motion.x - paddle.w / 2; + paddle.getCollisionRect().x = event.motion.x - paddle.getCollisionRect().w / 2; break; default: continue; } @@ -505,34 +277,9 @@ bool Game::Initialize() { return false; } - // Инициализация игрового состояния - paddleSpeedMultiplier = 1.0f; - ballSpeedMultiplier = 1.0f; - safetyNetExpireTime = 0; - stickyPaddle = true; - safetyNetActive = false; - - // Настройка рандомизации - std::srand(static_cast(SDL_GetTicks())); - // Инициализация игровых объектов ResetGame(); - // Начальные координаты каретки - paddle = { - SCREEN_WIDTH / 2 - BASE_PADDLE_WIDTH / 2, - SCREEN_HEIGHT - 2 * BASE_PADDLE_HEIGHT, - BASE_PADDLE_WIDTH, - BASE_PADDLE_HEIGHT - }; - - // Начальное состояние мяча - ball = { - SCREEN_WIDTH / 2 - BASE_BALL_WIDTH / 2, SCREEN_HEIGHT / 2 - BASE_BALL_HEIGHT / 2, - BASE_BALL_WIDTH, BASE_BALL_HEIGHT - }; - ballVelocity = {BASE_BALL_SPEED, -BASE_BALL_SPEED}; - return true; } diff --git a/lab1/src/bonuses/AccelerateBallBonus.cpp b/lab1/src/bonuses/AccelerateBallBonus.cpp new file mode 100644 index 0000000..3919a72 --- /dev/null +++ b/lab1/src/bonuses/AccelerateBallBonus.cpp @@ -0,0 +1,20 @@ +#include "bonuses/AccelerateBallBonus.hpp" + +void AccelerateBallBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); + activated = true; +} + +void AccelerateBallBonus::activate(GameState& game) { + game.ballSpeedMultiplier *= ACCELERATE_BALL_MULTIPLIER; + activated = true; + doRender = false; +} + +void AccelerateBallBonus::deactivate(GameState& game) { + game.ballSpeedMultiplier /= ACCELERATE_BALL_MULTIPLIER; + active = false; + activated = false; +} diff --git a/lab1/src/bonuses/Bonus.cpp b/lab1/src/bonuses/Bonus.cpp new file mode 100644 index 0000000..4fdeb2c --- /dev/null +++ b/lab1/src/bonuses/Bonus.cpp @@ -0,0 +1,11 @@ +#include "bonuses/Bonus.hpp" + +Bonus::Bonus(float x, float y) : rect{x, y, BONUS_WIDTH, BONUS_HEIGHT} {} + +void Bonus::update(float deltaTime, GameState& state) {\ + if (!doRender) return; + rect.y += fallSpeed * deltaTime; + if (rect.y > SCREEN_HEIGHT) { + active = false; + } +} diff --git a/lab1/src/bonuses/ExpandPaddleBonus.cpp b/lab1/src/bonuses/ExpandPaddleBonus.cpp new file mode 100644 index 0000000..ab635d3 --- /dev/null +++ b/lab1/src/bonuses/ExpandPaddleBonus.cpp @@ -0,0 +1,19 @@ +#include "bonuses/ExpandPaddleBonus.hpp" + +void ExpandPaddleBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); +} + +void ExpandPaddleBonus::activate(GameState& game) { + game.paddleWidthMultiplier *= EXPAND_PADDLE_MULTIPLIER; + activated = true; + doRender = false; +} + +void ExpandPaddleBonus::deactivate(GameState& game) { + game.paddleWidthMultiplier /= EXPAND_PADDLE_MULTIPLIER; + active = false; + activated = false; +} diff --git a/lab1/src/bonuses/RandomBallDirectionBonus.cpp b/lab1/src/bonuses/RandomBallDirectionBonus.cpp new file mode 100644 index 0000000..825d9df --- /dev/null +++ b/lab1/src/bonuses/RandomBallDirectionBonus.cpp @@ -0,0 +1,26 @@ +#include "bonuses/RandomBallDirectionBonus.hpp" + +#include + +void RandomBallDirectionBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); +} + +void RandomBallDirectionBonus::activate(GameState& game) { + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_real_distribution angleDist(0.0f, 2 * M_PI); + float angle = angleDist(gen); + float xMult = std::cos(angle); + float yMult = std::sin(angle); + game.ballVelocity.x *= xMult; + game.ballVelocity.y *= yMult; + deactivate(game); +} + +void RandomBallDirectionBonus::deactivate(GameState& game) { + active = false; + doRender = false; +} diff --git a/lab1/src/bonuses/SafetyNetBonus.cpp b/lab1/src/bonuses/SafetyNetBonus.cpp new file mode 100644 index 0000000..08ae08b --- /dev/null +++ b/lab1/src/bonuses/SafetyNetBonus.cpp @@ -0,0 +1,19 @@ +#include "bonuses/SafetyNetBonus.hpp" + +void SafetyNetBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); +} + +void SafetyNetBonus::activate(GameState& game) { + game.safetyNetActive = true; + activated = true; + doRender = false; +} + +void SafetyNetBonus::deactivate(GameState& game) { + game.safetyNetActive = false; + active = false; + activated = false; +} diff --git a/lab1/src/bonuses/ShrinkPaddleBonus.cpp b/lab1/src/bonuses/ShrinkPaddleBonus.cpp new file mode 100644 index 0000000..86f0997 --- /dev/null +++ b/lab1/src/bonuses/ShrinkPaddleBonus.cpp @@ -0,0 +1,20 @@ +#include "bonuses/ShrinkPaddleBonus.hpp" + +void ShrinkPaddleBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); +} + +void ShrinkPaddleBonus::activate(GameState& game) { + activated = true; + game.paddleWidthMultiplier /= SHRINK_PADDLE_MULTIPLIER; + doRender=false; + activated=true; +} + +void ShrinkPaddleBonus::deactivate(GameState& game) { + game.paddleWidthMultiplier *= SHRINK_PADDLE_MULTIPLIER; + active = false; + activated=false; +} diff --git a/lab1/src/bonuses/StickyPaddleBonus.cpp b/lab1/src/bonuses/StickyPaddleBonus.cpp new file mode 100644 index 0000000..7d97a7b --- /dev/null +++ b/lab1/src/bonuses/StickyPaddleBonus.cpp @@ -0,0 +1,27 @@ +#include "bonuses/StickyPaddleBonus.hpp" + +void StickyPaddleBonus::render(SDL_Renderer* renderer) { + if (!doRender) return; + SDL_SetRenderDrawColor(renderer, COLOR.r, COLOR.g, COLOR.b, COLOR.a); + SDL_RenderFillRect(renderer, &rect); +} + +void StickyPaddleBonus::activate(GameState& game) { + game.stickyPaddle = true; + activated = true; + doRender = false; +} + +void StickyPaddleBonus::deactivate(GameState& game) { + game.stickyPaddle = false; + active = false; +} + +void StickyPaddleBonus::expire(GameState& state) { + deactivate(state); +} + +void StickyPaddleBonus::update(float deltaTime, GameState& state) { + Bonus::update(deltaTime, state); + if (activated) IExpirable::update(deltaTime, state); +}