mirror of
https://github.com/lov3b/Pong.git
synced 2025-01-18 20:50:12 +01:00
Break out header file implementations to source files
This commit is contained in:
parent
c67345268e
commit
836ca94e1c
@ -22,22 +22,30 @@ if (NOT SDL2_TTF_LIBRARY)
|
|||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Define the executable target before linking libraries
|
# Define the executable target before linking libraries
|
||||||
add_executable(Pong src/main.cpp
|
add_executable(Pong src/main.cpp
|
||||||
src/SdlWrapper.h
|
src/SdlWrapper.h
|
||||||
|
src/SdlWrapper.cpp
|
||||||
src/Game.h
|
src/Game.h
|
||||||
|
src/Game.cpp
|
||||||
src/VisibleObjects/Ball.h
|
src/VisibleObjects/Ball.h
|
||||||
|
src/VisibleObjects/Ball.cpp
|
||||||
src/Vec2d/Vec2d.h
|
src/Vec2d/Vec2d.h
|
||||||
|
src/Vec2d/Vec2d.cpp
|
||||||
src/Vec2d/Bump.h
|
src/Vec2d/Bump.h
|
||||||
src/VisibleObjects/PlayerPaddle.h
|
src/VisibleObjects/PlayerPaddle.h
|
||||||
|
src/VisibleObjects/PlayerPaddle.cpp
|
||||||
src/VisibleObjects/Side.h
|
src/VisibleObjects/Side.h
|
||||||
src/text/TextScreen.h
|
src/text/TextScreen.h
|
||||||
|
src/text/TextScreen.cpp
|
||||||
src/defaultfont.h
|
src/defaultfont.h
|
||||||
|
src/defaultfont.cpp
|
||||||
src/icon.h
|
src/icon.h
|
||||||
src/icon.cpp
|
src/icon.cpp
|
||||||
src/text/OptionScreen.h
|
src/text/OptionScreen.h
|
||||||
|
src/text/OptionScreen.cpp
|
||||||
src/text/Score.h
|
src/text/Score.h
|
||||||
|
src/text/Score.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Now link the libraries to the target
|
# Now link the libraries to the target
|
||||||
|
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;
|
GameState gameState;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Game(SDL_Point screenSize) : SdlWrapper("Pong", screenSize, 60),
|
explicit Game(SDL_Point screenSize);
|
||||||
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() override {
|
~Game() override;
|
||||||
delete ball;
|
|
||||||
delete leftPaddle;
|
|
||||||
delete rightPaddle;
|
|
||||||
delete score;
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(SDL_Renderer *renderer) override {
|
void draw(SDL_Renderer *renderer) override;
|
||||||
// Background
|
|
||||||
SDL_SetRenderDrawColor(renderer, 128, 0, 128, 0);
|
|
||||||
SDL_RenderClear(renderer);
|
|
||||||
|
|
||||||
switch (gameState) {
|
void update() override;
|
||||||
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);
|
bool handleEvents() override;
|
||||||
}
|
|
||||||
|
|
||||||
void update() override {
|
void handleGameEvent(SDL_Event &event);
|
||||||
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;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
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;
|
bool running = true;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SdlWrapper(const char *title, const SDL_Point screenSize, const uint8_t fps) : fps(fps) {
|
explicit SdlWrapper(const char *title, SDL_Point screenSize, uint8_t fps);
|
||||||
this->screenSize = screenSize;
|
|
||||||
|
|
||||||
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
|
virtual ~SdlWrapper();
|
||||||
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);
|
int loop();
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
protected:
|
||||||
virtual void draw(SDL_Renderer *renderer) = 0;
|
virtual void draw(SDL_Renderer *renderer) = 0;
|
||||||
|
|
||||||
virtual void update() = 0;
|
virtual void update() = 0;
|
||||||
|
|
||||||
virtual bool handleEvents() = 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;
|
const float_t bumpSpeedIncrease = 1.05;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Vec2d(float_t hypotenuse) : hypotenuse(hypotenuse) {
|
explicit Vec2d(float_t 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);
|
void applyVector(Sint16 *ox, Sint16 *oy) const;
|
||||||
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 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:
|
public:
|
||||||
explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle,
|
explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle,
|
||||||
Score *score) : score(score), screen(screen), leftPaddle(leftPaddle), rightPaddle(rightPaddle),
|
Score *score);
|
||||||
x(screen->x / 2), y(screen->y / 2), vec2d(new Vec2d(6)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void resetPosition() {
|
void resetPosition();
|
||||||
this->x = screen->x / 2;
|
|
||||||
this->y = screen->y / 2;
|
|
||||||
|
|
||||||
delete vec2d;
|
void draw(SDL_Renderer *renderer) const;
|
||||||
vec2d = new Vec2d(6);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(SDL_Renderer *renderer) const {
|
void update();
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]]bool collidedScreenEdgeVertical() const {
|
[[nodiscard]] bool collidedScreenEdgeVertical() const;
|
||||||
return y - RADIUS <= 0 || y + RADIUS >= screen->y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::optional<Side> collidedScreenEdgeHorizontal() 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> collidedPaddle() 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL_rect.h>
|
||||||
|
#include <SDL_render.h>
|
||||||
#include "Side.h"
|
#include "Side.h"
|
||||||
#include "../Vec2d/Bump.h"
|
#include "../Vec2d/Bump.h"
|
||||||
|
|
||||||
@ -16,61 +13,20 @@ private:
|
|||||||
uint8_t color[4]{};
|
uint8_t color[4]{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PlayerPaddle(const SDL_Point *screen, const Side side) : SDL_Rect() , screen(screen){
|
PlayerPaddle(const SDL_Point *screen, Side side);
|
||||||
w = 20;
|
|
||||||
h = 80;
|
|
||||||
x = side == Side::LEFT ? 0 : screen->x - w;
|
|
||||||
y = (screen->y - h) / 2;
|
|
||||||
|
|
||||||
color[0] = 255;
|
[[nodiscard]] PaddleDirection getPaddleDirection() const;
|
||||||
color[1] = 234;
|
|
||||||
color[2] = 0;
|
|
||||||
color[3] = 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] PaddleDirection getPaddleDirection() const {
|
void draw(SDL_Renderer *renderer) const;
|
||||||
if (movingUp != movingDown)
|
|
||||||
return PaddleDirection::NOT_MOVING;
|
|
||||||
else if (movingUp)
|
|
||||||
return PaddleDirection::MOVING_UP;
|
|
||||||
return PaddleDirection::MOVING_DOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw(SDL_Renderer *renderer) {
|
void startMoving(bool up);
|
||||||
SDL_SetRenderDrawColor(renderer, color[0], color[1], color[2], color[3]);
|
|
||||||
SDL_RenderFillRect(renderer, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
void startMoving(const bool up) {
|
void stopMoving(bool up);
|
||||||
if (up)
|
|
||||||
movingUp = true;
|
|
||||||
else movingDown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopMoving(const bool up) {
|
void update();
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool canMoveDown() const {
|
[[nodiscard]] bool canMoveDown() const;
|
||||||
return y + h < screen->y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool canMoveUp() const {
|
|
||||||
return y > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
[[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
|
#pragma once
|
||||||
|
|
||||||
#include <filesystem>
|
const char *getDefaultFontPath();
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
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
|
#pragma once
|
||||||
|
|
||||||
#include "TextScreen.h"
|
#include "TextScreen.h"
|
||||||
#include "chrono"
|
#include "../VisibleObjects/Side.h"
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class OptionScreen : public TextScreen {
|
class OptionScreen : public TextScreen {
|
||||||
@ -32,35 +24,10 @@ public:
|
|||||||
return hasStartedCounting_;
|
return hasStartedCounting_;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
OptionScreen(const std::string &text, SDL_Point *screenSize, int seconds);
|
||||||
OptionScreen(const std::string &text, SDL_Point *screenSize, int seconds) : TextScreen(text, screenSize,
|
|
||||||
std::nullopt),
|
|
||||||
stepsToDo(seconds) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() override {
|
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 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 <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
class Score : public TextScreen {
|
class Score : public TextScreen {
|
||||||
private:
|
private:
|
||||||
@ -28,43 +29,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Score(SDL_Point *screenSize, uint8_t max_score) : MAX_SCORE(max_score), leftScore(0), rightScore(0),
|
explicit Score(SDL_Point *screenSize, uint8_t max_score);
|
||||||
TextScreen("", screenSize, std::make_optional(
|
|
||||||
SDL_Point{screenSize->x / 2 - 50, 10})) {
|
|
||||||
}
|
|
||||||
|
|
||||||
void update() override {
|
void update() override;
|
||||||
if (hasUpdated) return;
|
|
||||||
|
|
||||||
std::stringstream ss;
|
void resetScore();
|
||||||
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 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 <SDL_ttf.h>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
#include "../defaultfont.h"
|
#include "../defaultfont.h"
|
||||||
#include "iostream"
|
#include "iostream"
|
||||||
|
|
||||||
@ -39,129 +40,16 @@ public:
|
|||||||
* @param text This class takes care of freeing text
|
* @param text This class takes care of freeing text
|
||||||
* @param screenSize This won't be freed by this class
|
* @param screenSize This won't be freed by this class
|
||||||
*/
|
*/
|
||||||
TextScreen(const std::string &text, SDL_Point *screenSize, std::optional<SDL_Point> basePosition) : hasUpdated(
|
TextScreen(const std::string &text, SDL_Point *screenSize, std::optional<SDL_Point> basePosition);
|
||||||
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);
|
~TextScreen();
|
||||||
}
|
|
||||||
|
virtual void draw(SDL_Renderer *renderer);
|
||||||
|
|
||||||
|
void setText(const std::string &replaceText);
|
||||||
|
|
||||||
|
virtual void update();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initPositions(const std::string &text) {
|
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user