mirror of
				https://github.com/lov3b/Pong.git
				synced 2025-11-04 07:10:16 +01:00 
			
		
		
		
	Break out header file implementations to source files
This commit is contained in:
		@@ -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 ()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										142
									
								
								src/Game.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								src/Game.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										141
									
								
								src/Game.h
									
									
									
									
									
								
							
							
						
						
									
										141
									
								
								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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										63
									
								
								src/SdlWrapper.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/SdlWrapper.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										49
									
								
								src/Vec2d/Vec2d.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/Vec2d/Vec2d.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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<uint8_t> ints(0, 1);
 | 
			
		||||
    int sign = ints(mtRand) ? -1 : 1;
 | 
			
		||||
 | 
			
		||||
    std::uniform_real_distribution<float_t> 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<double_t>(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;
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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<uint8_t> ints(0, 1);
 | 
			
		||||
        int sign = ints(mtRand) ? -1 : 1;
 | 
			
		||||
    explicit Vec2d(float_t hypotenuse);
 | 
			
		||||
 | 
			
		||||
        std::uniform_real_distribution<float_t> 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<double_t>(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);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										64
									
								
								src/VisibleObjects/Ball.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/VisibleObjects/Ball.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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<Side> paddleSide = collidedPaddle();
 | 
			
		||||
    bool screenEdgeVertical = collidedScreenEdgeVertical();
 | 
			
		||||
    std::optional<Side> 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<Side> Ball::collidedScreenEdgeHorizontal() const {
 | 
			
		||||
    if (x + RADIUS >= screen->x)
 | 
			
		||||
        return Side::RIGHT;
 | 
			
		||||
    else if (x - RADIUS <= 0)
 | 
			
		||||
        return Side::LEFT;
 | 
			
		||||
    return std::nullopt;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::optional<Side> 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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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<Side> paddleSide = collidedPaddle();
 | 
			
		||||
        bool screenEdgeVertical = collidedScreenEdgeVertical();
 | 
			
		||||
        std::optional<Side> 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<Side> collidedScreenEdgeHorizontal() const {
 | 
			
		||||
        if (x + RADIUS >= screen->x)
 | 
			
		||||
            return Side::RIGHT;
 | 
			
		||||
        else if (x - RADIUS <= 0)
 | 
			
		||||
            return Side::LEFT;
 | 
			
		||||
        return std::nullopt;
 | 
			
		||||
    }
 | 
			
		||||
    [[nodiscard]] std::optional<Side> collidedScreenEdgeHorizontal() const;
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] std::optional<Side> 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<Side> collidedPaddle() const;
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								src/VisibleObjects/PlayerPaddle.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/VisibleObjects/PlayerPaddle.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,7 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by love on 2024-01-19.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <SDL_rect.h>
 | 
			
		||||
#include <SDL_render.h>
 | 
			
		||||
#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;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								src/defaultfont.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/defaultfont.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by Love on 2024-01-29.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#if defined(__LINUX__)
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -4,34 +4,4 @@
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <filesystem>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#include <SDL.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/text/OptionScreen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/text/OptionScreen.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
#include "OptionScreen.h"
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
int_least64_t getCurrentEpochTimeMillis() {
 | 
			
		||||
    using namespace std::chrono;
 | 
			
		||||
 | 
			
		||||
    auto now = system_clock::now();
 | 
			
		||||
    auto now_ms = time_point_cast<std::chrono::milliseconds>(now);
 | 
			
		||||
    auto epoch = now_ms.time_since_epoch();
 | 
			
		||||
    auto value = duration_cast<std::chrono::milliseconds>(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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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<std::chrono::milliseconds>(now);
 | 
			
		||||
    auto epoch = now_ms.time_since_epoch();
 | 
			
		||||
    auto value = std::chrono::duration_cast<std::chrono::milliseconds>(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();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								src/text/Score.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/text/Score.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,7 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										129
									
								
								src/text/TextScreen.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/text/TextScreen.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include "TextScreen.h"
 | 
			
		||||
#include "optional"
 | 
			
		||||
 | 
			
		||||
std::vector<std::string> splitString(const std::string &string, const char &delim) {
 | 
			
		||||
    int size = 0;
 | 
			
		||||
    for (char c: string)
 | 
			
		||||
        if (c == delim) size++;
 | 
			
		||||
 | 
			
		||||
    std::vector<std::string> 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<SDL_Point> 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<int>((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;
 | 
			
		||||
}
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
#include <SDL_ttf.h>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <optional>
 | 
			
		||||
#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<SDL_Point> 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<SDL_Point> 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<int>((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<std::string> splitString(const std::string &string, const char &delim) {
 | 
			
		||||
        int size = 0;
 | 
			
		||||
        for (char c: string)
 | 
			
		||||
            if (c == delim) size++;
 | 
			
		||||
 | 
			
		||||
        std::vector<std::string> 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);
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user