From 45ac2ffa68987052fc1fd041e7d010258aba07ec Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 19 Jan 2024 18:41:26 +0100 Subject: [PATCH] Score --- CMakeLists.txt | 12 +++- Game.h | 15 ++++- SdlWrapper.h | 14 ++++- VisibleObjects/Ball.h | 48 ++++++++++----- VisibleObjects/Score.h | 134 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 201 insertions(+), 22 deletions(-) create mode 100644 VisibleObjects/Score.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e7e2322..fecfbf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,13 @@ if (NOT SDL2_GFX_LIBRARY) message(FATAL_ERROR "SDL2_gfx not found") endif () +find_library(SDL2_TTF_LIBRARY NAMES SDL2_ttf SDL2_TTF SDL2TTF) +if (NOT SDL2_TTF_LIBRARY) + message(FATAL_ERROR "SDL2_TTF not found") +endif () + + + # Define the executable target before linking libraries add_executable(Pong main.cpp SdlWrapper.h @@ -22,7 +29,8 @@ add_executable(Pong main.cpp Vec2d/Vec2d.h Vec2d/Bump.h VisibleObjects/PlayerPaddle.h - VisibleObjects/Side.h) + VisibleObjects/Side.h + VisibleObjects/Score.h) # Now link the libraries to the target -target_link_libraries(Pong ${SDL2_LIBRARIES} ${SDL2_GFX_LIBRARY}) +target_link_libraries(Pong ${SDL2_LIBRARIES} ${SDL2_GFX_LIBRARY} ${SDL2_TTF_LIBRARY}) diff --git a/Game.h b/Game.h index 3ccca69..2a5d6c1 100644 --- a/Game.h +++ b/Game.h @@ -8,25 +8,34 @@ #include "SDL.h" #include "VisibleObjects/Ball.h" #include "VisibleObjects/PlayerPaddle.h" +#include "VisibleObjects/Score.h" class Game : public SdlWrapper { private: Ball *ball; - + Score *score; PlayerPaddle *leftPaddle, *rightPaddle; public: explicit Game(SDL_Point screenSize) : SdlWrapper("Pong", screenSize, 60) { leftPaddle = new PlayerPaddle(&this->screenSize, Side::LEFT); rightPaddle = new PlayerPaddle(&this->screenSize, Side::RIGHT); - ball = new Ball(&this->screenSize, leftPaddle, rightPaddle); + + auto func = [this](Side side) { + const char *player = side == Side::LEFT ? "one" : "two"; + std::cout << "Player " << player << " won" << std::endl; + this->running = false; + }; + score = new Score(5, &this->screenSize, func); + ball = new Ball(&this->screenSize, leftPaddle, rightPaddle, score); } ~Game() override { delete ball; delete leftPaddle; delete rightPaddle; + delete score; } void draw(SDL_Renderer *renderer) override { @@ -35,6 +44,7 @@ public: SDL_RenderClear(renderer); ball->draw(renderer); + score->draw(renderer); leftPaddle->draw(renderer); rightPaddle->draw(renderer); SDL_RenderPresent(renderer); @@ -44,6 +54,7 @@ public: ball->update(); leftPaddle->update(); rightPaddle->update(); + score->update(); return true; } diff --git a/SdlWrapper.h b/SdlWrapper.h index 976c069..105cefa 100644 --- a/SdlWrapper.h +++ b/SdlWrapper.h @@ -5,6 +5,8 @@ #pragma #include +#include +#include #include "SDL.h" class SdlWrapper { @@ -14,12 +16,20 @@ private: SDL_Renderer *renderer; protected: SDL_Point screenSize; + bool running = true; public: explicit SdlWrapper(const char *title, const SDL_Point screenSize, const uint8_t fps) : fps(fps) { this->screenSize = screenSize; - SDL_Init(SDL_INIT_VIDEO); + 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, @@ -48,7 +58,7 @@ public: virtual bool handleEvents() = 0; int loop() { - while (true) { + while (running) { if (!handleEvents() || !update()) break; draw(renderer); diff --git a/VisibleObjects/Ball.h b/VisibleObjects/Ball.h index 9f3634a..2d93d7c 100644 --- a/VisibleObjects/Ball.h +++ b/VisibleObjects/Ball.h @@ -9,6 +9,7 @@ #include #include #include "optional" +#include "Score.h" class Ball { private: @@ -18,15 +19,26 @@ private: Vec2d *vec2d; static const uint32_t color = 0xCD5C5CFF; const PlayerPaddle *leftPaddle, *rightPaddle; + Score *score; public: - explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle) { + explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle, + Score *score) { + this->score = score; this->screen = screen; - this->x = screen->x / 2; - this->y = screen->y / 2; - this->vec2d = new Vec2d(2.5); this->leftPaddle = leftPaddle; this->rightPaddle = rightPaddle; + this->x = screen->x / 2; + this->y = screen->y / 2; + vec2d = new Vec2d(4); + } + + void resetPosition() { + this->x = screen->x / 2; + this->y = screen->y / 2; + + delete vec2d; + vec2d = new Vec2d(4); } void draw(SDL_Renderer *renderer) const { @@ -34,16 +46,17 @@ public: } void update() { - std::cout << "Ball x: " << x << ", y: " << y << std::endl; 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 (collidedScreenEdgeHorizontal()) { - std::cout << "Player won" << std::endl; - exit(1); + } else if (scoreSide.has_value()) { + score->incrementScore(scoreSide.value()); + resetPosition(); } if (paddleSide.has_value()) { const PlayerPaddle *paddle = paddleSide.value() == Side::LEFT ? leftPaddle : rightPaddle; @@ -57,27 +70,30 @@ private: return y - RADIUS <= 0 || y + RADIUS >= screen->y; } - bool collidedScreenEdgeHorizontal() { - return x + RADIUS >= screen->x || x - RADIUS <= 0; + std::optional collidedScreenEdgeHorizontal() { + if (x + RADIUS >= screen->x) + return Side::RIGHT; + else if (x - RADIUS <= 0) + return Side::LEFT; + return std::nullopt; } std::optional collidedPaddle() { - // Check collision with right paddle + // Right paddle if (x + RADIUS >= rightPaddle->x && y >= rightPaddle->y && y <= rightPaddle->y + rightPaddle->h) { return Side::RIGHT; } - // Check collision with left paddle - else if (x - RADIUS <= leftPaddle->x + leftPaddle->w && - y >= leftPaddle->y && - y <= leftPaddle->y + leftPaddle->h) { + // 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/VisibleObjects/Score.h b/VisibleObjects/Score.h new file mode 100644 index 0000000..fae1ad3 --- /dev/null +++ b/VisibleObjects/Score.h @@ -0,0 +1,134 @@ +// +// Created by love on 2024-01-19. +// + +#pragma once + +#include +#include +#include +#include "Side.h" +#include "SDL_ttf.h" + + +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) +// Windows + const char* defaultFontPath = "C:\\Windows\\Fonts\\Arial.ttf"; +#elif defined(__linux__) +const char *defaultFontPath = "/usr/share/fonts/truetype/DejaVuSans-Bold.ttf"; +#else +// Other platforms + const char* defaultFontPath = "path/to/a/default/font.ttf"; +#endif + + +class Score { +private: + uint8_t leftScore, rightScore; + const uint8_t MAX_SCORE; + std::function &whenWon; + TTF_Font *font; + bool hasIncremented = false; + + // Regular + SDL_Rect position; + SDL_Color color = {243, 156, 18, 255}; + SDL_Surface *surface = nullptr; + + // Shadow + const SDL_Color shadowColor = {243, 156, 18, 100}; // Black color for the shadow + SDL_Surface *shadowSurface = nullptr; + const int shadowOffset = 3; + +public: + explicit Score(uint8_t max_score, SDL_Point *screenSize, std::function whenWon) : MAX_SCORE(max_score), + whenWon(whenWon) { + resetScore(); + this->font = TTF_OpenFont(defaultFontPath, 42); + if (font == nullptr) { + std::cerr << "Failed to load font: " << TTF_GetError() << std::endl; + exit(-1); + } + + this->position = SDL_Rect{screenSize->x / 2 - 50, 10, 100, 40}; + } + + ~Score() { + if (font) + TTF_CloseFont(font); + if (surface) + SDL_FreeSurface(surface); + } + + void update() { + if (!hasIncremented && surface != nullptr && shadowSurface != nullptr) return; + + if (surface != nullptr) + SDL_FreeSurface(surface); + if (shadowSurface != nullptr) + SDL_FreeSurface(shadowSurface); + + hasIncremented = false; + char score_text[8]; + sprintf(score_text, "%d - %d", leftScore, rightScore); + + // Create shadow surface + shadowSurface = TTF_RenderText_Solid(font, score_text, shadowColor); + if (shadowSurface == nullptr) { + std::cerr << "Failed to create shadow text surface: " << TTF_GetError() << std::endl; + } + + // Create main text surface + surface = TTF_RenderText_Solid(font, score_text, color); + if (surface == nullptr) { + std::cerr << "Failed to create text surface: " << TTF_GetError() << std::endl; + } + } + + void draw(SDL_Renderer *renderer) { + // Draw shadow + if (shadowSurface != nullptr) { + SDL_Texture *shadowTexture = SDL_CreateTextureFromSurface(renderer, shadowSurface); + if (shadowTexture != nullptr) { + SDL_Rect shadowPosition = {position.x + shadowOffset, position.y + shadowOffset, position.w, + position.h}; + SDL_RenderCopy(renderer, shadowTexture, NULL, &shadowPosition); + SDL_DestroyTexture(shadowTexture); + } + } + + // Draw text + if (surface != nullptr) { + SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); + if (texture != nullptr) { + SDL_RenderCopy(renderer, texture, NULL, &position); + SDL_DestroyTexture(texture); + } + } + } + + void resetScore() { + leftScore = rightScore = 0; + } + + void incrementScore(Side side) { + hasIncremented = true; + uint8_t temp; + switch (side) { + case Side::LEFT: + temp = ++leftScore; + break; + case Side::RIGHT: + temp = ++rightScore; + break; + } + + if (temp > MAX_SCORE) + whenWon(side); + } + +};