From f3530bf44c9fee19b9fe835d5995bd072cbbc859 Mon Sep 17 00:00:00 2001 From: Anastasia <95341361+Anleo1@users.noreply.github.com> Date: Tue, 20 May 2025 03:53:58 +0300 Subject: [PATCH] Add files via upload --- Block.cpp | 70 +++++++++ Block.h | 27 ++++ Bonus.cpp | 72 ++++++++++ Bonus.h | 23 +++ Constants.h | 23 +++ Game.cpp | 406 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Game.h | 59 ++++++++ Main.cpp | 7 + Types.h | 18 +++ 9 files changed, 705 insertions(+) create mode 100644 Block.cpp create mode 100644 Block.h create mode 100644 Bonus.cpp create mode 100644 Bonus.h create mode 100644 Constants.h create mode 100644 Game.cpp create mode 100644 Game.h create mode 100644 Main.cpp create mode 100644 Types.h diff --git a/Block.cpp b/Block.cpp new file mode 100644 index 0000000..eb0b1ab --- /dev/null +++ b/Block.cpp @@ -0,0 +1,70 @@ +#include "Block.h" + +Block::Block(float x, float y, BlockType t, int h, bool hb, BonusType bt): + type(t), health(h), maxHealth(h), hasBonus(hb), bonusType(bt), destroyed(false) +{ + shape.setPosition(x, y); + shape.setSize({ BLOCK_WIDTH, BLOCK_HEIGHT }); + shape.setOrigin(BLOCK_WIDTH / 2, BLOCK_HEIGHT / 2); + updateColor(); +} + +void Block::updateColor() { + switch (type) { + case BlockType::UNBREAKABLE: + shape.setFillColor(sf::Color(150, 150, 150)); + break; + case BlockType::REGULAR: { + float ratio = static_cast(health) / maxHealth; + sf::Uint8 green = static_cast(255 * ratio); + shape.setFillColor(sf::Color(0, green, 0)); + break; + } + case BlockType::SPEED_CHANGE: + shape.setFillColor(sf::Color(255, 255, 0)); + break; + case BlockType::WITH_BONUS: + shape.setFillColor(sf::Color(255, 0, 255)); + break; + } +} + +void Block::hit() { + if (type != BlockType::UNBREAKABLE) { + health--; + updateColor(); + if (health <= 0) { + destroyed = true; + } + } +} + +bool Block::isDestroyed() const { + return destroyed; +} + +sf::FloatRect Block::getBounds() const { + return shape.getGlobalBounds(); +} + +BlockType Block::getType() const { + return type; +} + +bool Block::hasBonusInside() const { + return hasBonus; +} + +BonusType Block::getBonusType() const { + return bonusType; +} + +sf::Vector2f Block::getPosition() const { + return shape.getPosition(); +} + +void Block::draw(sf::RenderWindow& window) const { + if (!destroyed) { + window.draw(shape); + } +} \ No newline at end of file diff --git a/Block.h b/Block.h new file mode 100644 index 0000000..ced66b4 --- /dev/null +++ b/Block.h @@ -0,0 +1,27 @@ +#pragma once +#include "Types.h" +#include "Constants.h" + +class Block { +public: + Block(float x, float y, BlockType t, int h = 1, bool hb = false, BonusType bt = BonusType::BOTTOM_SHIELD); + + void updateColor(); + void hit(); + bool isDestroyed() const; + sf::FloatRect getBounds() const; + BlockType getType() const; + bool hasBonusInside() const; + BonusType getBonusType() const; + sf::Vector2f getPosition() const; + void draw(sf::RenderWindow& window) const; + +private: + sf::RectangleShape shape; + BlockType type; + int health; + int maxHealth; + bool hasBonus; + BonusType bonusType; + bool destroyed; +}; \ No newline at end of file diff --git a/Bonus.cpp b/Bonus.cpp new file mode 100644 index 0000000..cd8784d --- /dev/null +++ b/Bonus.cpp @@ -0,0 +1,72 @@ +#include "Bonus.h" + +Bonus::Bonus(float x, float y, BonusType t) + : type(t), active(true), used(false) { + shape.setPosition(x, y); + shape.setSize({ BONUS_WIDTH, BONUS_HEIGHT }); + shape.setOrigin(BONUS_WIDTH / 2, BONUS_HEIGHT / 2); + + switch (type) { + case BonusType::PADDLE_SIZE_INCREASE: + shape.setFillColor(sf::Color(0, 255, 0)); + break; + case BonusType::PADDLE_SIZE_DECREASE: + shape.setFillColor(sf::Color(255, 0, 0)); + break; + case BonusType::BALL_SPEED_INCREASE: + shape.setFillColor(sf::Color(255, 165, 0)); + break; + case BonusType::BALL_SPEED_DECREASE: + shape.setFillColor(sf::Color(0, 255, 255)); + break; + case BonusType::STICKY_PADDLE: + shape.setFillColor(sf::Color(255, 190, 200)); + break; + case BonusType::BOTTOM_SHIELD: + shape.setFillColor(sf::Color(255, 255, 255)); + break; + case BonusType::RANDOM_TRAJECTORY_CHANGE: + shape.setFillColor(sf::Color(128, 0, 128)); + break; + } +} + +void Bonus::update(float deltaTime) { + if (isActive()) { + shape.move(0, BONUS_SPEED * deltaTime); + } +} + +void Bonus::draw(sf::RenderWindow& window) const { + if (isActive()) { + window.draw(shape); + } +} + +void Bonus::activate() { + active = true; +} + +void Bonus::deactivate() { + active = false; +} + +void Bonus::use() { + used = true; +} + +bool Bonus::isActive() const { + return active && !used; +} + +sf::FloatRect Bonus::getBounds() const { + return shape.getGlobalBounds(); +} + +BonusType Bonus::getType() const { + return type; +} + +sf::Vector2f Bonus::getPosition() const { + return shape.getPosition(); +} \ No newline at end of file diff --git a/Bonus.h b/Bonus.h new file mode 100644 index 0000000..8df2bc8 --- /dev/null +++ b/Bonus.h @@ -0,0 +1,23 @@ +#pragma once +#include "Types.h" +#include "Constants.h" + +class Bonus { +public: + Bonus(float x, float y, BonusType t); + void update(float deltaTime); + void draw(sf::RenderWindow& window) const; + void activate(); + void deactivate(); + void use(); + bool isActive() const; + sf::FloatRect getBounds() const; + BonusType getType() const; + sf::Vector2f getPosition() const; + +private: + sf::RectangleShape shape; + BonusType type; + bool active; + bool used; +}; \ No newline at end of file diff --git a/Constants.h b/Constants.h new file mode 100644 index 0000000..a53e3a5 --- /dev/null +++ b/Constants.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include + +#define M_PI 3.1415926535897932384f + +const unsigned int WINDOW_WIDTH = 800; +const unsigned int WINDOW_HEIGHT = 600; +const float INITIAL_PADDLE_WIDTH = 100.0; +const float PADDLE_HEIGHT = 20.0; +const float BALL_RADIUS = 10.0; +const float BLOCK_WIDTH = 80.0; +const float BLOCK_HEIGHT = 30.0; +const float BONUS_WIDTH = 30.0; +const float BONUS_HEIGHT = 20.0; +const float INITIAL_BALL_SPEED = 300.0; +const float PADDLE_SPEED = 500.0; +const float BONUS_SPEED = 150.0; +const int LOSE_PENALTY = 10; +const int WIN_SCORE = 50; +const unsigned int FONT_SIZE = 24; +const sf::Color TEXT_COLOR = sf::Color::White; +const int INITIAL_LIVES = 3; \ No newline at end of file diff --git a/Game.cpp b/Game.cpp new file mode 100644 index 0000000..dbf126b --- /dev/null +++ b/Game.cpp @@ -0,0 +1,406 @@ +#include "Game.h" +#include + +Game::Game() + : window(sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), "Arkanoid"), + ballSpeed(INITIAL_BALL_SPEED), + paddleWidth(INITIAL_PADDLE_WIDTH), + score(0), lives(INITIAL_LIVES), + gameOver(false), + gameWon(false), + stickyAvailable(false), + ballStuck(false), + bottomShieldActive(false), + rng(std::random_device{}()), dist(-0.3f, 0.3f) { + window.setFramerateLimit(60); + font.loadFromFile("C:/Windows/Fonts/Arial.ttf"); + + initGameObjects(); + initUI(); + createBlocks(); + resetBall(); +} + +void Game::setupText(sf::Text& text, const std::string& str, float x, float y, unsigned int size) { + text.setFont(font); + text.setString(str); + text.setCharacterSize(size); + text.setFillColor(TEXT_COLOR); + text.setPosition(x, y); +} + +void Game::initGameObjects() { + paddle.setSize({paddleWidth, PADDLE_HEIGHT}); + paddle.setOrigin(paddleWidth / 2, PADDLE_HEIGHT / 2); + paddle.setPosition(WINDOW_WIDTH / 2, WINDOW_HEIGHT - 50); + paddle.setFillColor(sf::Color::White); + + ball.setRadius(BALL_RADIUS); + ball.setOrigin(BALL_RADIUS, BALL_RADIUS); + ball.setPosition(WINDOW_WIDTH / 2, WINDOW_HEIGHT / 2); + ball.setFillColor(sf::Color::White); +} + +void Game::initUI() { + setupText(scoreText, "Score: 0", 10, 10); + setupText(livesText, "Lives: " + std::to_string(lives), WINDOW_WIDTH - 150, 10); + setupText(gameOverText, "GAME OVER! Press Q to restart", WINDOW_WIDTH / 2 - 280, WINDOW_HEIGHT / 2 - 20, 40); + setupText(winText, "YOU WIN! Press Q to restart", WINDOW_WIDTH / 2 - 280, WINDOW_HEIGHT / 2 - 20, 40); + setupText(stickyText, "Press SPACE to launch ball", WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT - 100, 20); + + bottomShieldIndicator.setSize({ 50, 5 }); + bottomShieldIndicator.setFillColor(sf::Color::Yellow); + bottomShieldIndicator.setPosition(WINDOW_WIDTH / 2 - 25, WINDOW_HEIGHT - 5); +} + +void Game::createBlocks() { + blocks.clear(); + activeBonuses.clear(); + + const int rows = 4; + const int cols = 8; + const float setX = 60.0; + const float setY = 50.0; + + for (int i = 0; i < rows; ++i) { + for (int j = 0; j < cols; ++j) { + float x = setX + j * (BLOCK_WIDTH + 10); + float y = setY + i * (BLOCK_HEIGHT + 10); + + BlockType type; + int health = 1; + bool hasBonus = false; + BonusType bonusType = BonusType::BOTTOM_SHIELD; + + int randVal = std::uniform_int_distribution<>(0, 9)(rng); + + if (randVal == 0) { + type = BlockType::UNBREAKABLE; + } + else if (randVal == 1) { + type = BlockType::SPEED_CHANGE; + } + else if (randVal == 2) { + type = BlockType::WITH_BONUS; + hasBonus = true; + bonusType = static_cast(std::uniform_int_distribution<>(0, 6)(rng)); + } + else { + type = BlockType::REGULAR; + health = std::uniform_int_distribution<>(1, 3)(rng); + } + blocks.push_back(std::make_unique(x, y, type, health, hasBonus, bonusType)); + } + } +} + +void Game::resetBall() { + ball.setPosition(paddle.getPosition().x, paddle.getPosition().y - paddle.getSize().y / 2 - BALL_RADIUS); + ballStuck = true; + ballVelocity = sf::Vector2f(0, 0); +} + +bool Game::checkWinCondition() { + return std::all_of(blocks.begin(), blocks.end(), [](const auto& block) { + return block->isDestroyed() || block->getType() == BlockType::UNBREAKABLE; + }); +} + +void Game::updateBall(float deltaTime) { + if (!ballStuck) { + ball.move(ballVelocity * deltaTime); + + if (ball.getPosition().x - BALL_RADIUS < 0) { + ball.setPosition(BALL_RADIUS, ball.getPosition().y); + ballVelocity.x = -ballVelocity.x; + } + + if (ball.getPosition().x + BALL_RADIUS > WINDOW_WIDTH) { + ball.setPosition(WINDOW_WIDTH - BALL_RADIUS, ball.getPosition().y); + ballVelocity.x = -ballVelocity.x; + } + + if (ball.getPosition().y - BALL_RADIUS < 0) { + ball.setPosition(ball.getPosition().x, BALL_RADIUS); + ballVelocity.y = -ballVelocity.y; + } + + if (ball.getPosition().y + BALL_RADIUS > WINDOW_HEIGHT) { + handleBallLoss(); + } + } +} + +void Game::handleBallLoss() { + if (bottomShieldActive) { + bottomShieldActive = false; + ball.setPosition(ball.getPosition().x, WINDOW_HEIGHT - BALL_RADIUS); + ballVelocity.y = -std::abs(ballVelocity.y); + } + else { + lives--; + score -= LOSE_PENALTY; + + if (lives <= 0) { + gameOver = true; + } + else { + resetBall(); + } + } +} + +void Game::checkCollisions() { + if (ball.getGlobalBounds().intersects(paddle.getGlobalBounds())) { + handlePaddleCollision(); + } + + for (auto& block : blocks) { + if (!block->isDestroyed() && ball.getGlobalBounds().intersects(block->getBounds())) { + handleBlockCollision(*block); + break; + } + } +} + +void Game::handlePaddleCollision() { + float hitPos = (ball.getPosition().x - paddle.getPosition().x) / (paddle.getSize().x / 2); + float angle = hitPos * (M_PI / 4); + + float speed = std::sqrt(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y); + ballVelocity.x = std::sin(angle) * speed; + ballVelocity.y = -std::cos(angle) * speed; + + if (stickyAvailable) { + ballStuck = true; + stickyAvailable = false; + } +} + +void Game::handleBlockCollision(Block& block) { + sf::FloatRect ballRect = ball.getGlobalBounds(); + sf::FloatRect blockRect = block.getBounds(); + + float overlapLeft = ballRect.left + ballRect.width - blockRect.left; + float overlapRight = blockRect.left + blockRect.width - ballRect.left; + float overlapTop = ballRect.top + ballRect.height - blockRect.top; + float overlapBottom = blockRect.top + blockRect.height - ballRect.top; + + bool fromLeft = std::abs(overlapLeft) < std::abs(overlapRight); + bool fromTop = std::abs(overlapTop) < std::abs(overlapBottom); + + float minOverlapX = fromLeft ? overlapLeft : overlapRight; + float minOverlapY = fromTop ? overlapTop : overlapBottom; + + if (std::abs(minOverlapX) < std::abs(minOverlapY)) { + if (fromLeft) { + ballVelocity.x = -std::abs(ballVelocity.x); + } + else { + ballVelocity.x = std::abs(ballVelocity.x); + } + } + else { + if (fromTop) { + ballVelocity.y = -std::abs(ballVelocity.y); + } + else { + ballVelocity.y = std::abs(ballVelocity.y); + } + } + + block.hit(); + if (block.getType() != BlockType::UNBREAKABLE) { + if (block.hasBonusInside() && block.isDestroyed()) { + sf::Vector2f pos = block.getPosition(); + activeBonuses.push_back(std::make_unique(pos.x, pos.y, block.getBonusType())); + } + + if (block.getType() == BlockType::SPEED_CHANGE) { + score += 4; + ballSpeed *= 1.2f; + ballVelocity = ballVelocity / std::sqrt(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y) * ballSpeed; + } + score += 1; + } +} + +void Game::updateBonuses(float deltaTime) { + for (auto bonusIter = activeBonuses.begin(); bonusIter != activeBonuses.end();) { + auto& currentBonus = *bonusIter; + currentBonus->update(deltaTime); + + bool collectedByPaddle = currentBonus->getBounds().intersects(paddle.getGlobalBounds()); + bool fellOffScreen = currentBonus->getPosition().y > WINDOW_HEIGHT; + + if (collectedByPaddle) { + applyBonus(currentBonus->getType()); + bonusIter = activeBonuses.erase(bonusIter); + } + else if (fellOffScreen) { + bonusIter = activeBonuses.erase(bonusIter); + } + else { + ++bonusIter; + } + } +} + +void Game::applyBonus(BonusType type) { + switch (type) { + score += 4; + case BonusType::PADDLE_SIZE_INCREASE: + paddleWidth = std::min(paddleWidth * 1.5f, INITIAL_PADDLE_WIDTH * 2.0f); + paddle.setSize({ paddleWidth, PADDLE_HEIGHT }); + paddle.setOrigin(paddleWidth / 2, PADDLE_HEIGHT / 2); + break; + + case BonusType::PADDLE_SIZE_DECREASE: + paddleWidth = std::max(paddleWidth * 0.75f, INITIAL_PADDLE_WIDTH * 0.5f); + paddle.setSize({ paddleWidth, PADDLE_HEIGHT }); + paddle.setOrigin(paddleWidth / 2, PADDLE_HEIGHT / 2); + break; + + case BonusType::BALL_SPEED_INCREASE: + ballSpeed *= 1.2f; + ballVelocity = ballVelocity / std::sqrt(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y) * ballSpeed; + break; + + case BonusType::BALL_SPEED_DECREASE: + ballSpeed *= 0.8f; + ballVelocity = ballVelocity / std::sqrt(ballVelocity.x * ballVelocity.x + ballVelocity.y * ballVelocity.y) * ballSpeed; + break; + + case BonusType::STICKY_PADDLE: + stickyAvailable = true; + break; + + case BonusType::BOTTOM_SHIELD: + bottomShieldActive = true; + break; + + case BonusType::RANDOM_TRAJECTORY_CHANGE: + float randomAngle = std::uniform_real_distribution(-M_PI/6.0, M_PI/6.0 )(rng); + ballVelocity = sf::Vector2f(std::sin(randomAngle) * ballSpeed, -std::cos(randomAngle) * ballSpeed); + break; + } +} + +void Game::updateUI() { + scoreText.setString("Score: " + std::to_string(score)); + livesText.setString("Lives: " + std::to_string(lives)); +} + +void Game::render() { + window.clear(); + window.draw(paddle); + window.draw(ball); + + for (const auto& block : blocks) { + block->draw(window); + } + + for (const auto& bonus : activeBonuses) { + bonus->draw(window); + } + + window.draw(scoreText); + window.draw(livesText); + + if (bottomShieldActive) { + window.draw(bottomShieldIndicator); + } + + if (ballStuck) { + window.draw(stickyText); + } + + if (gameOver) { + window.draw(gameOverText); + } + else if (gameWon) { + window.draw(winText); + } + window.display(); +} + +void Game::initGame() { + ballSpeed = INITIAL_BALL_SPEED; + paddleWidth = INITIAL_PADDLE_WIDTH; + stickyAvailable = false; + ballStuck = false; + bottomShieldActive = false; + score = 0; + lives = INITIAL_LIVES; + gameOver = false; + gameWon = false; + + initGameObjects(); + createBlocks(); + resetBall(); + updateUI(); +} + +void Game::run() { + sf::Clock clock; + + while (window.isOpen()) { + sf::Time deltaTime = clock.restart(); + processEvents(); + + if (!gameOver && !gameWon) { + update(deltaTime.asSeconds()); + + if (checkWinCondition() || score >= WIN_SCORE) { + gameWon = true; + } + } + render(); + } +} + +void Game::processEvents() { + sf::Event event; + while (window.pollEvent(event)) { + if (event.type == sf::Event::Closed) { + window.close(); + } + if (event.type == sf::Event::KeyPressed) { + if (event.key.code == sf::Keyboard::Space && ballStuck) { + ballStuck = false; + float angle = dist(rng); + ballVelocity.x = std::sin(angle) * ballSpeed; + ballVelocity.y = -std::abs(std::cos(angle)) * ballSpeed; + } + else if (event.key.code == sf::Keyboard::Q && (gameOver || gameWon)) { + initGame(); + } + } + } +} + +void Game::update(float deltaTime) { + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left) && + paddle.getPosition().x - paddle.getSize().x / 2 > 0) { + paddle.move(-PADDLE_SPEED * deltaTime, 0); + if (ballStuck) { + ball.setPosition(paddle.getPosition().x, paddle.getPosition().y - paddle.getSize().y / 2 - BALL_RADIUS); + } + } + + if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && + paddle.getPosition().x + paddle.getSize().x / 2 < WINDOW_WIDTH) { + paddle.move(PADDLE_SPEED * deltaTime, 0); + if (ballStuck) { + ball.setPosition(paddle.getPosition().x, paddle.getPosition().y - paddle.getSize().y / 2 - BALL_RADIUS); + } + } + + if (!ballStuck) { + updateBall(deltaTime); + checkCollisions(); + } + + updateBonuses(deltaTime); + updateUI(); +} \ No newline at end of file diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..4dc30e7 --- /dev/null +++ b/Game.h @@ -0,0 +1,59 @@ +#pragma once +#include +#include +#include +#include "Block.h" +#include "Bonus.h" +#include "Constants.h" + +class Game { +public: + Game(); + void run(); + +private: + void processEvents(); + void update(float deltaTime); + void render(); + void setupText(sf::Text& text, const std::string& str, float x, float y, unsigned int size = FONT_SIZE); + void initGameObjects(); + void initUI(); + void createBlocks(); + void resetBall(); + bool checkWinCondition(); + void updateBall(float deltaTime); + void handleBallLoss(); + void checkCollisions(); + void handlePaddleCollision(); + void handleBlockCollision(Block& block); + void updateBonuses(float deltaTime); + void applyBonus(BonusType type); + void updateUI(); + void initGame(); + + sf::RenderWindow window; + sf::Font font; + sf::RectangleShape paddle; + sf::CircleShape ball; + std::vector> blocks; + std::vector> activeBonuses; + sf::Vector2f ballVelocity; + float ballSpeed; + float paddleWidth; + int score; + int lives; + bool gameOver; + bool gameWon; + bool stickyAvailable; + bool ballStuck; + bool bottomShieldActive; + std::mt19937 rng; + std::uniform_real_distribution dist; + + sf::Text scoreText; + sf::Text livesText; + sf::Text gameOverText; + sf::Text winText; + sf::Text stickyText; + sf::RectangleShape bottomShieldIndicator; +}; \ No newline at end of file diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..c1fc21d --- /dev/null +++ b/Main.cpp @@ -0,0 +1,7 @@ +#include "Game.h" + +int main() { + Game game; + game.run(); + return 0; +} \ No newline at end of file diff --git a/Types.h b/Types.h new file mode 100644 index 0000000..d08a4c8 --- /dev/null +++ b/Types.h @@ -0,0 +1,18 @@ +#pragma once + +enum class BlockType { + UNBREAKABLE, + REGULAR, + SPEED_CHANGE, + WITH_BONUS +}; + +enum class BonusType { + PADDLE_SIZE_INCREASE, + PADDLE_SIZE_DECREASE, + BALL_SPEED_INCREASE, + BALL_SPEED_DECREASE, + STICKY_PADDLE, + BOTTOM_SHIELD, + RANDOM_TRAJECTORY_CHANGE +}; \ No newline at end of file