Break out header file implementations to source files

This commit is contained in:
Love 2024-01-29 16:19:04 +01:00
parent c67345268e
commit 836ca94e1c
20 changed files with 685 additions and 579 deletions

View File

@ -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
View 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;
}
}
}

View File

@ -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
View 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;
}

View File

@ -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
View 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;
}
}
}

View File

@ -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);
}; };

View 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;
}

View File

@ -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;
}
}; };

View 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;
}

View File

@ -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
View 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
}

View File

@ -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
View 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;
}

View File

@ -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
View 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;
}

View File

@ -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
View 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;
}

View File

@ -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;
}
}; };