diff --git a/CMakeLists.txt b/CMakeLists.txt index 425f858..3b9a920 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,33 +22,41 @@ if (NOT SDL2_TTF_LIBRARY) endif () - # Define the executable target before linking libraries add_executable(Pong src/main.cpp src/SdlWrapper.h + src/SdlWrapper.cpp src/Game.h + src/Game.cpp src/VisibleObjects/Ball.h + src/VisibleObjects/Ball.cpp src/Vec2d/Vec2d.h + src/Vec2d/Vec2d.cpp src/Vec2d/Bump.h src/VisibleObjects/PlayerPaddle.h + src/VisibleObjects/PlayerPaddle.cpp src/VisibleObjects/Side.h src/text/TextScreen.h + src/text/TextScreen.cpp src/defaultfont.h + src/defaultfont.cpp src/icon.h src/icon.cpp src/text/OptionScreen.h + src/text/OptionScreen.cpp src/text/Score.h + src/text/Score.cpp ) # Now link the libraries to the target target_link_libraries(Pong ${SDL2_LIBRARIES} ${SDL2_GFX_LIBRARY} ${SDL2_TTF_LIBRARY}) # Set compiler optimization flags -if(ENABLE_OPTIMIZATIONS) +if (ENABLE_OPTIMIZATIONS) message(STATUS "Optimizations are enabled") - if(MSVC) + if (MSVC) target_compile_options(Pong PRIVATE /O2) - else() + else () target_compile_options(Pong PRIVATE -O3) - endif() -endif() + endif () +endif () diff --git a/src/Game.cpp b/src/Game.cpp new file mode 100644 index 0000000..fbbed5f --- /dev/null +++ b/src/Game.cpp @@ -0,0 +1,142 @@ +#include "Game.h" + +Game::Game(SDL_Point screenSize) : SdlWrapper("Pong", screenSize, 60), + leftPaddle(new PlayerPaddle(&this->screenSize, Side::LEFT)), + rightPaddle(new PlayerPaddle(&this->screenSize, Side::RIGHT)), + score(new Score(&this->screenSize, 5)), + ball(new Ball(&this->screenSize, leftPaddle, rightPaddle, score)), + startScreen( + new OptionScreen("Welcome to Pong!\nPress any key to get started...", + &this->screenSize, 4)), endScreen(nullptr), + gameState(GameState::START_SCREEN) { +} + +Game::~Game() { + delete ball; + delete leftPaddle; + delete rightPaddle; + delete score; +} + +void Game::draw(SDL_Renderer *const renderer) { + // Background + SDL_SetRenderDrawColor(renderer, 128, 0, 128, 0); + SDL_RenderClear(renderer); + + switch (gameState) { + case GameState::START_SCREEN: + startScreen->draw(renderer); + break; + case GameState::GAME: + ball->draw(renderer); + score->draw(renderer); + leftPaddle->draw(renderer); + rightPaddle->draw(renderer); + break; + case GameState::END_SCREEN: + endScreen->draw(renderer); + break; + } + + SDL_RenderPresent(renderer); +} + +void Game::update() { + switch (gameState) { + case GameState::START_SCREEN: + startScreen->update(); + if (startScreen->isDone()) { + gameState = GameState::GAME; + delete startScreen; // We will never get back to this state + } + break; + case GameState::GAME: + ball->update(); + leftPaddle->update(); + rightPaddle->update(); + score->update(); + + if (score->sideWon().has_value()) { + const char *player = score->sideWon().value() == Side::LEFT ? "left" : "right"; + std::stringstream ss; + ss << "The " << player << " player won with " << std::to_string(score->leftScore) << " - " + << std::to_string(score->rightScore) << "\nWould you like to play again?" + << "\nIf so, press any button..."; + score->resetScore(); + endScreen = new OptionScreen(ss.str(), &screenSize, 4); + gameState = GameState::END_SCREEN; + } + break; + case GameState::END_SCREEN: + endScreen->update(); + if (endScreen->isDone()) { + gameState = GameState::GAME; + delete endScreen; // The text will not be the same if we get back here. We might as well free it. + } + break; + } +} + +bool Game::handleEvents() { + SDL_Event event; + + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) + return false; + + switch (gameState) { + case GameState::START_SCREEN: + if (event.type == SDL_KEYDOWN && !startScreen->hasStartedCounting()) + startScreen->startCountDown(); + + break; + case GameState::GAME: + handleGameEvent(event); + break; + case GameState::END_SCREEN: + if (event.type == SDL_KEYDOWN && !endScreen->hasStartedCounting()) + endScreen->startCountDown(); + break; + } + } + + return true; +} + +void Game::handleGameEvent(SDL_Event &event) { + if (event.type == SDL_KEYDOWN) { + switch (event.key.keysym.sym) { + case SDLK_w: + leftPaddle->startMoving(true); + break; + case SDLK_s: + leftPaddle->startMoving(false); + break; + + case SDLK_UP: + rightPaddle->startMoving(true); + break; + case SDLK_DOWN: + rightPaddle->startMoving(false); + break; + } + } else if (event.type == SDL_KEYUP) { + switch (event.key.keysym.sym) { + case SDLK_w: + leftPaddle->stopMoving(true); + break; + case SDLK_s: + leftPaddle->stopMoving(false); + break; + + case SDLK_UP: + rightPaddle->stopMoving(true); + break; + case SDLK_DOWN: + rightPaddle->stopMoving(false); + break; + + } + + } +} \ No newline at end of file diff --git a/src/Game.h b/src/Game.h index 8ec9656..a6ecc31 100644 --- a/src/Game.h +++ b/src/Game.h @@ -28,144 +28,15 @@ protected: GameState gameState; public: - explicit Game(SDL_Point screenSize) : SdlWrapper("Pong", screenSize, 60), - leftPaddle(new PlayerPaddle(&this->screenSize, Side::LEFT)), - rightPaddle(new PlayerPaddle(&this->screenSize, Side::RIGHT)), - score(new Score(&this->screenSize, 5)), - ball(new Ball(&this->screenSize, leftPaddle, rightPaddle, score)), - startScreen( - new OptionScreen("Welcome to Pong!\nPress any key to get started...", - &this->screenSize, 4)), endScreen(nullptr), - gameState(GameState::START_SCREEN) { - } + explicit Game(SDL_Point screenSize); - ~Game() override { - delete ball; - delete leftPaddle; - delete rightPaddle; - delete score; - } + ~Game() override; - void draw(SDL_Renderer *renderer) override { - // Background - SDL_SetRenderDrawColor(renderer, 128, 0, 128, 0); - SDL_RenderClear(renderer); + void draw(SDL_Renderer *renderer) override; - switch (gameState) { - case GameState::START_SCREEN: - startScreen->draw(renderer); - break; - case GameState::GAME: - ball->draw(renderer); - score->draw(renderer); - leftPaddle->draw(renderer); - rightPaddle->draw(renderer); - break; - case GameState::END_SCREEN: - endScreen->draw(renderer); - break; - } + void update() override; - SDL_RenderPresent(renderer); - } + bool handleEvents() override; - void update() override { - switch (gameState) { - case GameState::START_SCREEN: - startScreen->update(); - if (startScreen->isDone()) { - gameState = GameState::GAME; - delete startScreen; // We will never get back to this state - } - break; - case GameState::GAME: - ball->update(); - leftPaddle->update(); - rightPaddle->update(); - score->update(); - - if (score->sideWon().has_value()) { - const char *player = score->sideWon().value() == Side::LEFT ? "left" : "right"; - std::stringstream ss; - ss << "The " << player << " player won with " << std::to_string(score->leftScore) << " - " - << std::to_string(score->rightScore) << "\nWould you like to play again?" - << "\nIf so, press any button..."; - score->resetScore(); - endScreen = new OptionScreen(ss.str(), &screenSize, 4); - gameState = GameState::END_SCREEN; - } - break; - case GameState::END_SCREEN: - endScreen->update(); - if (endScreen->isDone()) { - gameState = GameState::GAME; - delete endScreen; // The text will not be the same if we get back here. We might as well free it. - } - break; - } - } - - bool handleEvents() override { - SDL_Event event; - - while (SDL_PollEvent(&event)) { - if (event.type == SDL_QUIT) - return false; - - switch (gameState) { - case GameState::START_SCREEN: - if (event.type == SDL_KEYDOWN && !startScreen->hasStartedCounting()) - startScreen->startCountDown(); - - break; - case GameState::GAME: - handleGameEvent(event); - break; - case GameState::END_SCREEN: - if (event.type == SDL_KEYDOWN && !endScreen->hasStartedCounting()) - endScreen->startCountDown(); - break; - } - } - - return true; - } - - void handleGameEvent(SDL_Event &event) { - if (event.type == SDL_KEYDOWN) { - switch (event.key.keysym.sym) { - case SDLK_w: - leftPaddle->startMoving(true); - break; - case SDLK_s: - leftPaddle->startMoving(false); - break; - - case SDLK_UP: - rightPaddle->startMoving(true); - break; - case SDLK_DOWN: - rightPaddle->startMoving(false); - break; - } - } else if (event.type == SDL_KEYUP) { - switch (event.key.keysym.sym) { - case SDLK_w: - leftPaddle->stopMoving(true); - break; - case SDLK_s: - leftPaddle->stopMoving(false); - break; - - case SDLK_UP: - rightPaddle->stopMoving(true); - break; - case SDLK_DOWN: - rightPaddle->stopMoving(false); - break; - - } - - } - } + void handleGameEvent(SDL_Event &event); }; \ No newline at end of file diff --git a/src/SdlWrapper.cpp b/src/SdlWrapper.cpp new file mode 100644 index 0000000..79b04f5 --- /dev/null +++ b/src/SdlWrapper.cpp @@ -0,0 +1,63 @@ + +#include "SdlWrapper.h" + +SdlWrapper::SdlWrapper(const char *const title, const SDL_Point screenSize, const uint8_t fps) : fps(fps), + screenSize( + screenSize) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl; + exit(-1); + } + if (TTF_Init() < 0) { + std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; + exit(-1); + } + window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screenSize.x, screenSize.y, + 0); + if (window == nullptr) { + std::cerr << "Failed to create SDL_Window with error: " << SDL_GetError() << std::endl; + exit(-1); + } + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + if (renderer == nullptr) { + std::cerr << "Failed to create SDL_Renderer with error: " << SDL_GetError() << std::endl; + exit(-1); + } + + SDL_RWops *ops = SDL_RWFromConstMem(icon, iconLength); + if (ops == nullptr) { + std::cerr << "Failed to load from constant memory with error: " << SDL_GetError() << std::endl; + exit(-1); + } + iconSurface = SDL_LoadBMP_RW(ops, 1); + if (iconSurface == nullptr) { + std::cerr << "Failed to load BMP from SDL_RWops with error: " << SDL_GetError() << std::endl; + exit(-1); + } + SDL_SetWindowIcon(window, iconSurface); + + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); +} + +SdlWrapper::~SdlWrapper() { + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_FreeSurface(iconSurface); + SDL_QuitSubSystem(SDL_INIT_VIDEO); + SDL_Quit(); +} + +int SdlWrapper::loop() { + while (running) { + if (!handleEvents()) + break; + update(); + draw(renderer); + SDL_Delay(1000 / fps); + } + + return 1; +} diff --git a/src/SdlWrapper.h b/src/SdlWrapper.h index 2ff70fc..d6ba790 100644 --- a/src/SdlWrapper.h +++ b/src/SdlWrapper.h @@ -21,69 +21,16 @@ protected: bool running = true; public: - explicit SdlWrapper(const char *title, const SDL_Point screenSize, const uint8_t fps) : fps(fps) { - this->screenSize = screenSize; + explicit SdlWrapper(const char *title, SDL_Point screenSize, uint8_t fps); - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - std::cerr << "Failed to initialize SDL: " << SDL_GetError() << std::endl; - exit(-1); - } - if (TTF_Init() < 0) { - std::cerr << "Failed to initialize TTF: " << TTF_GetError() << std::endl; - exit(-1); - } - window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screenSize.x, screenSize.y, - 0); - if (window == nullptr) { - std::cerr << "Failed to create SDL_Window with error: " << SDL_GetError() << std::endl; - exit(-1); - } - renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); - if (renderer == nullptr) { - std::cerr << "Failed to create SDL_Renderer with error: " << SDL_GetError() << std::endl; - exit(-1); - } + virtual ~SdlWrapper(); - SDL_RWops *ops = SDL_RWFromConstMem(icon, iconLength); - if (ops == nullptr) { - std::cerr << "Failed to load from constant memory with error: " << SDL_GetError() << std::endl; - exit(-1); - } - iconSurface = SDL_LoadBMP_RW(ops, 1); - if (iconSurface == nullptr) { - std::cerr << "Failed to load BMP from SDL_RWops with error: " << SDL_GetError() << std::endl; - exit(-1); - } - SDL_SetWindowIcon(window, iconSurface); - - - SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); - SDL_RenderClear(renderer); - SDL_RenderPresent(renderer); - } - - virtual ~SdlWrapper() { - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_FreeSurface(iconSurface); - SDL_Quit(); - } + int loop(); +protected: virtual void draw(SDL_Renderer *renderer) = 0; virtual void update() = 0; virtual bool handleEvents() = 0; - - int loop() { - while (running) { - if (!handleEvents()) - break; - update(); - draw(renderer); - SDL_Delay(1000 / fps); - } - - return 1; - } }; diff --git a/src/Vec2d/Vec2d.cpp b/src/Vec2d/Vec2d.cpp new file mode 100644 index 0000000..b3f2299 --- /dev/null +++ b/src/Vec2d/Vec2d.cpp @@ -0,0 +1,49 @@ +#include "Vec2d.h" + +Vec2d::Vec2d(float_t hypotenuse) : hypotenuse(hypotenuse) { + std::random_device rd; + mtRand = std::mt19937(rd()); + std::uniform_int_distribution ints(0, 1); + int sign = ints(mtRand) ? -1 : 1; + + std::uniform_real_distribution angleGen(15, 75); + double_t degrees = angleGen(mtRand); + double_t angle = toRadians(degrees); + x = sin(angle) * sign * hypotenuse; + y = cos(angle) * sign * hypotenuse; + + smallAngleGen = std::uniform_real_distribution(15, 20); +} + +void Vec2d::applyVector(Sint16 *ox, Sint16 *oy) const { + *ox += std::round(x); + *oy += std::round(y); +} + +void Vec2d::bump(BumpType bumpType, PaddleDirection paddleDirection) { + // Make everything a bit faster so it's not boring + hypotenuse *= bumpSpeedIncrease; + x *= bumpSpeedIncrease; + y *= bumpSpeedIncrease; + + switch (bumpType) { + case BumpType::BOTH: + x = -x; + y = -y; + break; + case BumpType::WALL: + y = -y; + break; + case BumpType::PADDLE: + x = -x; + double angle = 0; + if (paddleDirection != PaddleDirection::NOT_MOVING) { + double_t degrees = smallAngleGen(mtRand); + degrees *= paddleDirection == PaddleDirection::MOVING_UP ? -1 : 1; + angle = toRadians(degrees); + + // Adjusting y direction based on the angle + y = sin(angle) * hypotenuse; + } + } +} diff --git a/src/Vec2d/Vec2d.h b/src/Vec2d/Vec2d.h index 798718d..07fe395 100644 --- a/src/Vec2d/Vec2d.h +++ b/src/Vec2d/Vec2d.h @@ -24,53 +24,9 @@ private: const float_t bumpSpeedIncrease = 1.05; public: - Vec2d(float_t hypotenuse) : hypotenuse(hypotenuse) { - std::random_device rd; - mtRand = std::mt19937(rd()); - std::uniform_int_distribution ints(0, 1); - int sign = ints(mtRand) ? -1 : 1; + explicit Vec2d(float_t hypotenuse); - std::uniform_real_distribution angleGen(15, 75); - double_t degrees = angleGen(mtRand); - double_t angle = toRadians(degrees); - x = sin(angle) * sign * hypotenuse; - y = cos(angle) * sign * hypotenuse; - - smallAngleGen = std::uniform_real_distribution(15, 20); - } - - void applyVector(Sint16 *ox, Sint16 *oy) const { - *ox += std::round(x); - *oy += std::round(y); - } - - void bump(BumpType bumpType, PaddleDirection paddleDirection) { - // Make everything a bit faster so it's not boring - hypotenuse *= bumpSpeedIncrease; - x *= bumpSpeedIncrease; - y *= bumpSpeedIncrease; - - switch (bumpType) { - case BumpType::BOTH: - x = -x; - y = -y; - break; - case BumpType::WALL: - y = -y; - break; - case BumpType::PADDLE: - x = -x; - double angle = 0; - if (paddleDirection != PaddleDirection::NOT_MOVING) { - double_t degrees = smallAngleGen(mtRand); - degrees *= paddleDirection == PaddleDirection::MOVING_UP ? -1 : 1; - angle = toRadians(degrees); - - // Adjusting y direction based on the angle - y = sin(angle) * hypotenuse; - } - } - - } + void applyVector(Sint16 *ox, Sint16 *oy) const; + void bump(BumpType bumpType, PaddleDirection paddleDirection); }; \ No newline at end of file diff --git a/src/VisibleObjects/Ball.cpp b/src/VisibleObjects/Ball.cpp new file mode 100644 index 0000000..9a5d8b3 --- /dev/null +++ b/src/VisibleObjects/Ball.cpp @@ -0,0 +1,64 @@ +#include "Ball.h" + + +Ball::Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle, Score *score) + : score(score), screen(screen), leftPaddle(leftPaddle), rightPaddle(rightPaddle), + x(screen->x / 2), y(screen->y / 2), vec2d(new Vec2d(6)) { +} + +void Ball::resetPosition() { + this->x = screen->x / 2; + this->y = screen->y / 2; + + delete vec2d; + vec2d = new Vec2d(6); +} + +void Ball::draw(SDL_Renderer *const renderer) const { + filledCircleColor(renderer, x, y, RADIUS, color); +} + +void Ball::update() { + std::optional paddleSide = collidedPaddle(); + bool screenEdgeVertical = collidedScreenEdgeVertical(); + std::optional scoreSide = collidedScreenEdgeHorizontal(); + + if (screenEdgeVertical && paddleSide.has_value()) { + vec2d->bump(BumpType::BOTH, PaddleDirection::NONE); + } else if (screenEdgeVertical) { + vec2d->bump(BumpType::WALL, PaddleDirection::NONE); + } else if (scoreSide.has_value()) { + // Invert side + score->incrementScore(scoreSide.value() == Side::LEFT ? Side::RIGHT : Side::LEFT); + resetPosition(); + } + if (paddleSide.has_value()) { + const PlayerPaddle *paddle = paddleSide.value() == Side::LEFT ? leftPaddle : rightPaddle; + vec2d->bump(BumpType::PADDLE, paddle->getPaddleDirection()); + } + vec2d->applyVector(&x, &y); +} + +bool Ball::collidedScreenEdgeVertical() const { + return y - RADIUS <= 0 || y + RADIUS >= screen->y; +} + +std::optional Ball::collidedScreenEdgeHorizontal() const { + if (x + RADIUS >= screen->x) + return Side::RIGHT; + else if (x - RADIUS <= 0) + return Side::LEFT; + return std::nullopt; +} + +std::optional Ball::collidedPaddle() const { + // Right paddle + if (x + RADIUS >= rightPaddle->x && y >= rightPaddle->y && y <= rightPaddle->y + rightPaddle->h) { + return Side::RIGHT; + } + // Left paddle + if (x - RADIUS <= leftPaddle->x + leftPaddle->w && y >= leftPaddle->y && y <= leftPaddle->y + leftPaddle->h) { + return Side::LEFT; + } + return std::nullopt; +} diff --git a/src/VisibleObjects/Ball.h b/src/VisibleObjects/Ball.h index 8d2b1e6..44b50d3 100644 --- a/src/VisibleObjects/Ball.h +++ b/src/VisibleObjects/Ball.h @@ -23,67 +23,20 @@ private: public: explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle, - Score *score) : score(score), screen(screen), leftPaddle(leftPaddle), rightPaddle(rightPaddle), - x(screen->x / 2), y(screen->y / 2), vec2d(new Vec2d(6)) { - } + Score *score); - void resetPosition() { - this->x = screen->x / 2; - this->y = screen->y / 2; + void resetPosition(); - delete vec2d; - vec2d = new Vec2d(6); - } + void draw(SDL_Renderer *renderer) const; - void draw(SDL_Renderer *renderer) const { - filledCircleColor(renderer, x, y, RADIUS, color); - } - - void update() { - std::optional paddleSide = collidedPaddle(); - bool screenEdgeVertical = collidedScreenEdgeVertical(); - std::optional scoreSide = collidedScreenEdgeHorizontal(); - - if (screenEdgeVertical && paddleSide.has_value()) { - vec2d->bump(BumpType::BOTH, PaddleDirection::NONE); - } else if (screenEdgeVertical) { - vec2d->bump(BumpType::WALL, PaddleDirection::NONE); - } else if (scoreSide.has_value()) { - // Invert side - score->incrementScore(scoreSide.value() == Side::LEFT ? Side::RIGHT : Side::LEFT); - resetPosition(); - } - if (paddleSide.has_value()) { - const PlayerPaddle *paddle = paddleSide.value() == Side::LEFT ? leftPaddle : rightPaddle; - vec2d->bump(BumpType::PADDLE, paddle->getPaddleDirection()); - } - vec2d->applyVector(&x, &y); - } + void update(); private: - [[nodiscard]]bool collidedScreenEdgeVertical() const { - return y - RADIUS <= 0 || y + RADIUS >= screen->y; - } + [[nodiscard]] bool collidedScreenEdgeVertical() const; - [[nodiscard]] std::optional collidedScreenEdgeHorizontal() const { - if (x + RADIUS >= screen->x) - return Side::RIGHT; - else if (x - RADIUS <= 0) - return Side::LEFT; - return std::nullopt; - } + [[nodiscard]] std::optional collidedScreenEdgeHorizontal() const; - [[nodiscard]] std::optional collidedPaddle() const { - // Right paddle - if (x + RADIUS >= rightPaddle->x && y >= rightPaddle->y && y <= rightPaddle->y + rightPaddle->h) { - return Side::RIGHT; - } - // Left paddle - if (x - RADIUS <= leftPaddle->x + leftPaddle->w && y >= leftPaddle->y && y <= leftPaddle->y + leftPaddle->h) { - return Side::LEFT; - } - return std::nullopt; - } + [[nodiscard]] std::optional collidedPaddle() const; }; diff --git a/src/VisibleObjects/PlayerPaddle.cpp b/src/VisibleObjects/PlayerPaddle.cpp new file mode 100644 index 0000000..fcf6ed3 --- /dev/null +++ b/src/VisibleObjects/PlayerPaddle.cpp @@ -0,0 +1,58 @@ +#include "PlayerPaddle.h" + + +PlayerPaddle::PlayerPaddle(const SDL_Point *screen, const Side side) : SDL_Rect(), screen(screen) { + w = 20; + h = 80; + x = side == Side::LEFT ? 0 : screen->x - w; + y = (screen->y - h) / 2; + + color[0] = 255; + color[1] = 234; + color[2] = 0; + color[3] = 255; +} + +[[nodiscard]] PaddleDirection PlayerPaddle::getPaddleDirection() const { + if (movingUp != movingDown) + return PaddleDirection::NOT_MOVING; + else if (movingUp) + return PaddleDirection::MOVING_UP; + return PaddleDirection::MOVING_DOWN; +} + +void PlayerPaddle::draw(SDL_Renderer *renderer) const { + SDL_SetRenderDrawColor(renderer, color[0], color[1], color[2], color[3]); + SDL_RenderFillRect(renderer, this); +} + +void PlayerPaddle::update() { + // We cannot move up and down + if (movingUp == movingDown) + return; + + if (movingUp && canMoveUp()) + y -= MOVE_PER_TICK; + else if (movingDown && canMoveDown()) + y += MOVE_PER_TICK; +} + +void PlayerPaddle::startMoving(const bool up) { + if (up) + movingUp = true; + else movingDown = true; +} + +void PlayerPaddle::stopMoving(const bool up) { + if (up) + movingUp = false; + else movingDown = false; +} + +[[nodiscard]] bool PlayerPaddle::canMoveDown() const { + return y + h < screen->y; +} + +[[nodiscard]] bool PlayerPaddle::canMoveUp() const { + return y > 0; +} \ No newline at end of file diff --git a/src/VisibleObjects/PlayerPaddle.h b/src/VisibleObjects/PlayerPaddle.h index 47888d1..e65ad85 100644 --- a/src/VisibleObjects/PlayerPaddle.h +++ b/src/VisibleObjects/PlayerPaddle.h @@ -1,10 +1,7 @@ -// -// Created by love on 2024-01-19. -// - #pragma once - +#include +#include #include "Side.h" #include "../Vec2d/Bump.h" @@ -16,61 +13,20 @@ private: uint8_t color[4]{}; public: - PlayerPaddle(const SDL_Point *screen, const Side side) : SDL_Rect() , screen(screen){ - w = 20; - h = 80; - x = side == Side::LEFT ? 0 : screen->x - w; - y = (screen->y - h) / 2; + PlayerPaddle(const SDL_Point *screen, Side side); - color[0] = 255; - color[1] = 234; - color[2] = 0; - color[3] = 255; - } + [[nodiscard]] PaddleDirection getPaddleDirection() const; - [[nodiscard]] PaddleDirection getPaddleDirection() const { - if (movingUp != movingDown) - return PaddleDirection::NOT_MOVING; - else if (movingUp) - return PaddleDirection::MOVING_UP; - return PaddleDirection::MOVING_DOWN; - } + void draw(SDL_Renderer *renderer) const; - void draw(SDL_Renderer *renderer) { - SDL_SetRenderDrawColor(renderer, color[0], color[1], color[2], color[3]); - SDL_RenderFillRect(renderer, this); - } + void startMoving(bool up); - void startMoving(const bool up) { - if (up) - movingUp = true; - else movingDown = true; - } + void stopMoving(bool up); - void stopMoving(const bool up) { - if (up) - movingUp = false; - else movingDown = false; - } - - void update() { - // We cannot move up and down - if (movingUp == movingDown) - return; - - if (movingUp && canMoveUp()) - y -= MOVE_PER_TICK; - else if (movingDown && canMoveDown()) - y += MOVE_PER_TICK; - } + void update(); private: - [[nodiscard]] bool canMoveDown() const { - return y + h < screen->y; - } - - [[nodiscard]] bool canMoveUp() const { - return y > 0; - } + [[nodiscard]] bool canMoveDown() const; + [[nodiscard]] bool canMoveUp() const; }; diff --git a/src/defaultfont.cpp b/src/defaultfont.cpp new file mode 100644 index 0000000..309486e --- /dev/null +++ b/src/defaultfont.cpp @@ -0,0 +1,34 @@ +// +// Created by Love on 2024-01-29. +// + +#if defined(__LINUX__) +#include + +const char *getLinuxFilePath() { + const char *fonts[] = {"/usr/share/fonts/truetype/DejaVuSans-Bold.ttf", // openSUSE + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", // Debian + "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", // Arch + "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf", // Fedora + }; + for (const char *font: fonts) + if (std::filesystem::exists(font)) + return font; + + return nullptr; +} +#endif + + +const char *getDefaultFontPath() { +#if defined(_WIN32) || defined(_WIN64) + return R"(C:\Windows\Fonts\Arial.ttf)"; +#elif defined(__linux__) + return getLinuxFilePath(); +#elif defined(__APPLE__) || defined(__MACH__) + return "/System/Library/Fonts/Supplemental/Arial.ttf"; +#else + return nullptr; +#endif +} + diff --git a/src/defaultfont.h b/src/defaultfont.h index 3180970..a2a826a 100644 --- a/src/defaultfont.h +++ b/src/defaultfont.h @@ -4,34 +4,4 @@ #pragma once -#include - - -const char *getLinuxFilePath() { - const char *fonts[] = {"/usr/share/fonts/truetype/DejaVuSans-Bold.ttf", // openSUSE - "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", // Debian - "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", // Arch - "/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf", // Fedora - }; - for (const char *font: fonts) - if (std::filesystem::exists(font)) - return font; - - return nullptr; -} - -const char *getDefaultFontPath() { -#if defined(_WIN32) || defined(_WIN64) - return "C:\\Windows\\Fonts\\Arial.ttf"; -#elif defined(__linux__) - return getLinuxFilePath(); -#elif defined(__APPLE__) || defined(__MACH__) - return "/System/Library/Fonts/Supplemental/Arial.ttf"; -#else - return nullptr; -#endif -} - - - - +const char *getDefaultFontPath(); \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 03066f5..07c0cf7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,7 @@ #include -extern "C" int main(int _argc, char* _argv[]) { +extern "C" int main(int _argc, char *_argv[]) { Game game(SDL_Point{1000, 600}); return game.loop(); diff --git a/src/text/OptionScreen.cpp b/src/text/OptionScreen.cpp new file mode 100644 index 0000000..a782b83 --- /dev/null +++ b/src/text/OptionScreen.cpp @@ -0,0 +1,42 @@ +#include "OptionScreen.h" +#include + + +int_least64_t getCurrentEpochTimeMillis() { + using namespace std::chrono; + + auto now = system_clock::now(); + auto now_ms = time_point_cast(now); + auto epoch = now_ms.time_since_epoch(); + auto value = duration_cast(epoch); + return value.count(); +} + +OptionScreen::OptionScreen(const std::string &text, SDL_Point *screenSize, int seconds) : TextScreen(text, screenSize, + std::nullopt), + stepsToDo(seconds) { +} + +void OptionScreen::update() { + auto now = getCurrentEpochTimeMillis(); + if (hasStartedCounting_ && nextMsEpoch <= now) { + if (stepsDone < stepsToDo) { + std::string s = std::to_string(stepsToDo - stepsDone); + setText(s); + nextMsEpoch = now + 1000; + hasUpdated = false; + stepsDone++; + } else { + isDone_ = true; + } + } + + TextScreen::update(); +} + +void OptionScreen::startCountDown() { + auto epochMs = getCurrentEpochTimeMillis(); + nextMsEpoch = epochMs + 1000; + hasStartedCounting_ = true; + stepsDone = 0; +} \ No newline at end of file diff --git a/src/text/OptionScreen.h b/src/text/OptionScreen.h index c496b38..27d165d 100644 --- a/src/text/OptionScreen.h +++ b/src/text/OptionScreen.h @@ -5,15 +5,7 @@ #pragma once #include "TextScreen.h" -#include "chrono" - -int_least64_t getCurrentEpochTimeMillis() { - auto now = std::chrono::system_clock::now(); - auto now_ms = std::chrono::time_point_cast(now); - auto epoch = now_ms.time_since_epoch(); - auto value = std::chrono::duration_cast(epoch); - return value.count(); -} +#include "../VisibleObjects/Side.h" class OptionScreen : public TextScreen { @@ -32,35 +24,10 @@ public: return hasStartedCounting_; } -public: - OptionScreen(const std::string &text, SDL_Point *screenSize, int seconds) : TextScreen(text, screenSize, - std::nullopt), - stepsToDo(seconds) { - } + OptionScreen(const std::string &text, SDL_Point *screenSize, int seconds); - void update() override { - auto now = getCurrentEpochTimeMillis(); - if (hasStartedCounting_ && nextMsEpoch <= now) { - if (stepsDone < stepsToDo) { - std::string s = std::to_string(stepsToDo - stepsDone); - setText(s); - nextMsEpoch = now + 1000; - hasUpdated = false; - stepsDone++; - } else { - isDone_ = true; - } - } - - TextScreen::update(); - } - - void startCountDown() { - auto epochMs = getCurrentEpochTimeMillis(); - nextMsEpoch = epochMs + 1000; - hasStartedCounting_ = true; - stepsDone = 0; - } + void update() override; + void startCountDown(); }; diff --git a/src/text/Score.cpp b/src/text/Score.cpp new file mode 100644 index 0000000..ace84a9 --- /dev/null +++ b/src/text/Score.cpp @@ -0,0 +1,39 @@ +#include "Score.h" + +Score::Score(SDL_Point *screenSize, uint8_t max_score) : MAX_SCORE(max_score), leftScore(0), rightScore(0), + TextScreen("", screenSize, std::make_optional( + SDL_Point{screenSize->x / 2 - 50, 10})) { +} + +void Score::update() { + if (hasUpdated) return; + + std::stringstream ss; + ss << std::to_string(leftScore) << " - " << std::to_string(rightScore); + setText(ss.str()); + TextScreen::update(); + + hasUpdated = true; +} + +void Score::resetScore() { + leftScore = rightScore = 0; + sideWon_ = std::nullopt; + hasUpdated = false; +} + +void Score::incrementScore(const Side side) { + hasUpdated = false; + uint8_t incrementedScore; + switch (side) { + case Side::LEFT: + incrementedScore = ++leftScore; + break; + case Side::RIGHT: + incrementedScore = ++rightScore; + break; + } + + if (incrementedScore > MAX_SCORE) + sideWon_ = side; +} \ No newline at end of file diff --git a/src/text/Score.h b/src/text/Score.h index 73b5f25..2f46d21 100644 --- a/src/text/Score.h +++ b/src/text/Score.h @@ -14,6 +14,7 @@ #include #include #include +#include class Score : public TextScreen { private: @@ -28,43 +29,12 @@ public: } public: - explicit Score(SDL_Point *screenSize, uint8_t max_score) : MAX_SCORE(max_score), leftScore(0), rightScore(0), - TextScreen("", screenSize, std::make_optional( - SDL_Point{screenSize->x / 2 - 50, 10})) { - } + explicit Score(SDL_Point *screenSize, uint8_t max_score); - void update() override { - if (hasUpdated) return; + void update() override; - std::stringstream ss; - ss << std::to_string(leftScore) << " - " << std::to_string(rightScore); - setText(ss.str()); - TextScreen::update(); - - hasUpdated = true; - } - - void resetScore() { - leftScore = rightScore = 0; - sideWon_ = std::nullopt; - hasUpdated = false; - } - - void incrementScore(const Side side) { - hasUpdated = false; - uint8_t incrementedScore; - switch (side) { - case Side::LEFT: - incrementedScore = ++leftScore; - break; - case Side::RIGHT: - incrementedScore = ++rightScore; - break; - } - - if (incrementedScore > MAX_SCORE) - sideWon_ = side; - } + void resetScore(); + void incrementScore(const Side side); }; diff --git a/src/text/TextScreen.cpp b/src/text/TextScreen.cpp new file mode 100644 index 0000000..448ea73 --- /dev/null +++ b/src/text/TextScreen.cpp @@ -0,0 +1,129 @@ +#include +#include +#include "TextScreen.h" +#include "optional" + +std::vector splitString(const std::string &string, const char &delim) { + int size = 0; + for (char c: string) + if (c == delim) size++; + + std::vector lines; + lines.reserve(size); + + std::stringstream ss(string); + std::string line; + while (std::getline(ss, line, delim)) + lines.push_back(line); + + + return lines; +} + +TextScreen::TextScreen(const std::string &text, SDL_Point *const screenSize, std::optional basePosition) + : hasUpdated(false), screenSize(screenSize), basePosition(basePosition) { + const char *defaultFontPath = getDefaultFontPath(); + if (defaultFontPath == nullptr) { + std::cerr << "Font path is not set for this platform (null)" << std::endl; + exit(-1); + } + font = TTF_OpenFont(defaultFontPath, 42); + if (font == nullptr) { + std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + exit(-1); + } + + initPositions(text); +} + +void TextScreen::initPositions(const std::string &text) { + lines = splitString(text, '\n'); + surfaces.clear(); + shadowSurfaces.clear(); + positions.clear(); + shadowPositions.clear(); + + surfaces.reserve(lines.size()); + shadowSurfaces.reserve(lines.size()); + positions.reserve(lines.size()); + shadowPositions.reserve(lines.size()); + + for (int i = 0; i < lines.size(); ++i) { + int textWidth, textHeight; + TTF_SizeText(font, lines[i].c_str(), &textWidth, &textHeight); + + SDL_Point base = basePosition.has_value() ? basePosition.value() : SDL_Point{(screenSize->x - textWidth) / 2, + static_cast((screenSize->y - + textHeight * + (lines.size())) / + 2)}; + + SDL_Rect regularPosition = {base.x, base.y + textHeight * i, textWidth, textHeight}; + SDL_Rect shadowPosition = {base.x + shadowOffset, base.y + textHeight * i + shadowOffset, textWidth, + textHeight}; + positions.push_back(regularPosition); + shadowPositions.push_back(shadowPosition); + } +} + +TextScreen::~TextScreen() { + // TTF_CLoseFont & SDL_FreeSurface are null-safe + + TTF_CloseFont(font); + for (auto *surface: surfaces) + SDL_FreeSurface(surface); + for (auto *surface: shadowSurfaces) + SDL_FreeSurface(surface); +} + +void TextScreen::draw(SDL_Renderer *const renderer) { + for (int i = 0; i < surfaces.size(); ++i) { + // Draw shadow + SDL_Texture *shadowTexture = SDL_CreateTextureFromSurface(renderer, shadowSurfaces[i]); + if (shadowTexture != nullptr) { + SDL_RenderCopy(renderer, shadowTexture, nullptr, &shadowPositions[i]); + SDL_DestroyTexture(shadowTexture); + } + + // Draw text + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surfaces[i]); + if (texture != nullptr) { + SDL_RenderCopy(renderer, texture, nullptr, &positions[i]); + SDL_DestroyTexture(texture); + } + } +} + +void TextScreen::setText(const std::string &replaceText) { + lines = splitString(replaceText, '\n'); + initPositions(replaceText); +} + + +void TextScreen::update() { + if (hasUpdated) + return; + + for (auto &surface: surfaces) + SDL_FreeSurface(surface); + for (auto &shadowSurface: shadowSurfaces) + SDL_FreeSurface(shadowSurface); + surfaces.clear(); + shadowSurfaces.clear(); + + for (const auto &line: lines) { + SDL_Surface *textSurface = TTF_RenderText_Solid(font, line.c_str(), color); + SDL_Surface *shadowSurface = TTF_RenderText_Solid(font, line.c_str(), shadowColor); + + if (textSurface == nullptr || shadowSurface == nullptr) { + std::cerr << "Failed to create text surface (TextScreen): " << TTF_GetError() << std::endl; + continue; + } + + surfaces.push_back(textSurface); + shadowSurfaces.push_back(shadowSurface); + + } + + hasUpdated = true; +} \ No newline at end of file diff --git a/src/text/TextScreen.h b/src/text/TextScreen.h index 289ec39..7ef5fa0 100644 --- a/src/text/TextScreen.h +++ b/src/text/TextScreen.h @@ -8,6 +8,7 @@ #include #include #include +#include #include "../defaultfont.h" #include "iostream" @@ -39,129 +40,16 @@ public: * @param text This class takes care of freeing text * @param screenSize This won't be freed by this class */ - TextScreen(const std::string &text, SDL_Point *screenSize, std::optional basePosition) : hasUpdated( - false), screenSize(screenSize), basePosition(basePosition) { - const char *defaultFontPath = getDefaultFontPath(); - if (defaultFontPath == nullptr) { - std::cerr << "Font path is not set for this platform (null)" << std::endl; - exit(-1); - } - font = TTF_OpenFont(defaultFontPath, 42); - if (font == nullptr) { - std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; - exit(-1); - } + TextScreen(const std::string &text, SDL_Point *screenSize, std::optional basePosition); - initPositions(text); - } + ~TextScreen(); + + virtual void draw(SDL_Renderer *renderer); + + void setText(const std::string &replaceText); + + virtual void update(); private: - void initPositions(const std::string &text) { - lines = splitString(text, '\n'); - surfaces.clear(); - shadowSurfaces.clear(); - positions.clear(); - shadowPositions.clear(); - - surfaces.reserve(lines.size()); - shadowSurfaces.reserve(lines.size()); - positions.reserve(lines.size()); - shadowPositions.reserve(lines.size()); - - for (int i = 0; i < lines.size(); ++i) { - int textWidth, textHeight; - TTF_SizeText(font, lines[i].c_str(), &textWidth, &textHeight); - - SDL_Point base = basePosition.has_value() ? basePosition.value() : SDL_Point{ - (screenSize->x - textWidth) / 2, - static_cast((screenSize->y - textHeight * (lines.size())) / 2)}; - - SDL_Rect regularPosition = {base.x, base.y + textHeight * i, textWidth, textHeight}; - SDL_Rect shadowPosition = {base.x + shadowOffset, base.y + textHeight * i + shadowOffset, textWidth, - textHeight}; - positions.push_back(regularPosition); - shadowPositions.push_back(shadowPosition); - } - } - -public: - ~TextScreen() { - // TTF_CLoseFont & SDL_FreeSurface are null-safe - - TTF_CloseFont(font); - for (auto *surface: surfaces) - SDL_FreeSurface(surface); - for (auto *surface: shadowSurfaces) - SDL_FreeSurface(surface); - } - - - virtual void draw(SDL_Renderer *renderer) { - for (int i = 0; i < surfaces.size(); ++i) { - // Draw shadow - SDL_Texture *shadowTexture = SDL_CreateTextureFromSurface(renderer, shadowSurfaces[i]); - if (shadowTexture != nullptr) { - SDL_RenderCopy(renderer, shadowTexture, nullptr, &shadowPositions[i]); - SDL_DestroyTexture(shadowTexture); - } - - // Draw text - SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surfaces[i]); - if (texture != nullptr) { - SDL_RenderCopy(renderer, texture, nullptr, &positions[i]); - SDL_DestroyTexture(texture); - } - } - } - - void setText(const std::string &replaceText) { - lines = splitString(replaceText, '\n'); - initPositions(replaceText); - } - - virtual void update() { - if (hasUpdated) - return; - - for (auto &surface: surfaces) - SDL_FreeSurface(surface); - for (auto &shadowSurface: shadowSurfaces) - SDL_FreeSurface(shadowSurface); - surfaces.clear(); - shadowSurfaces.clear(); - - for (const auto &line: lines) { - SDL_Surface *textSurface = TTF_RenderText_Solid(font, line.c_str(), color); - SDL_Surface *shadowSurface = TTF_RenderText_Solid(font, line.c_str(), shadowColor); - - if (textSurface == nullptr || shadowSurface == nullptr) { - std::cerr << "Failed to create text surface (TextScreen): " << TTF_GetError() << std::endl; - continue; - } - - surfaces.push_back(textSurface); - shadowSurfaces.push_back(shadowSurface); - - } - - hasUpdated = true; - } - -private: - static std::vector splitString(const std::string &string, const char &delim) { - int size = 0; - for (char c: string) - if (c == delim) size++; - - std::vector lines; - lines.reserve(size); - - std::stringstream ss(string); - std::string line; - while (std::getline(ss, line, delim)) - lines.push_back(line); - - - return lines; - } + void initPositions(const std::string &text); };