mirror of
https://github.com/lov3b/Pong.git
synced 2025-01-18 04:30:11 +01:00
Break out header file implementations to source files
This commit is contained in:
parent
c67345268e
commit
836ca94e1c
@ -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);
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user