commit 30235c5fc826b6dc2e34042353f8ae25a600e679 Author: = <=> Date: Fri Jan 19 15:06:48 2024 +0100 physics input works diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e24b65 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +cmake-build-debug diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e7e2322 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.27) +project(Pong) + +set(CMAKE_CXX_STANDARD 17) + +# Base SDL2 +find_package(SDL2 REQUIRED) +include_directories(${SDL2_INCLUDE_DIRS}) + +# SDL2 Gfx +find_library(SDL2_GFX_LIBRARY NAMES SDL2_gfx SDL2_gfxd) +if (NOT SDL2_GFX_LIBRARY) + message(FATAL_ERROR "SDL2_gfx not found") +endif () + +# Define the executable target before linking libraries +add_executable(Pong main.cpp + SdlWrapper.h + Game.h + VisibleObjects/Ball.h + utils.h + Vec2d/Vec2d.h + Vec2d/Bump.h + VisibleObjects/PlayerPaddle.h + VisibleObjects/Side.h) + +# Now link the libraries to the target +target_link_libraries(Pong ${SDL2_LIBRARIES} ${SDL2_GFX_LIBRARY}) diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..3ccca69 --- /dev/null +++ b/Game.h @@ -0,0 +1,95 @@ +// +// Created by love on 2024-01-18. +// + +#pragma + +#include "SdlWrapper.h" +#include "SDL.h" +#include "VisibleObjects/Ball.h" +#include "VisibleObjects/PlayerPaddle.h" + + +class Game : public SdlWrapper { +private: + Ball *ball; + + PlayerPaddle *leftPaddle, *rightPaddle; + +public: + explicit Game(SDL_Point screenSize) : SdlWrapper("Pong", screenSize, 60) { + leftPaddle = new PlayerPaddle(&this->screenSize, Side::LEFT); + rightPaddle = new PlayerPaddle(&this->screenSize, Side::RIGHT); + ball = new Ball(&this->screenSize, leftPaddle, rightPaddle); + } + + ~Game() override { + delete ball; + delete leftPaddle; + delete rightPaddle; + } + + void draw(SDL_Renderer *renderer) override { + // Background + SDL_SetRenderDrawColor(renderer, 128, 0, 128, 0); + SDL_RenderClear(renderer); + + ball->draw(renderer); + leftPaddle->draw(renderer); + rightPaddle->draw(renderer); + SDL_RenderPresent(renderer); + } + + bool update() override { + ball->update(); + leftPaddle->update(); + rightPaddle->update(); + + return true; + } + + bool handleEvents() override { + SDL_Event event; + while (SDL_PollEvent(&event) != 0) { + if (event.type == SDL_QUIT) + return false; + + 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; + } + + } + + } + return true; + } +}; \ No newline at end of file diff --git a/SdlWrapper.h b/SdlWrapper.h new file mode 100644 index 0000000..976c069 --- /dev/null +++ b/SdlWrapper.h @@ -0,0 +1,60 @@ +// +// Created by love on 2024-01-18. +// + +#pragma + +#include +#include "SDL.h" + +class SdlWrapper { +private: + const uint8_t fps = 60; + SDL_Window *window; + SDL_Renderer *renderer; +protected: + SDL_Point screenSize; + +public: + explicit SdlWrapper(const char *title, const SDL_Point screenSize, const uint8_t fps) : fps(fps) { + this->screenSize = screenSize; + + SDL_Init(SDL_INIT_VIDEO); + window = SDL_CreateWindow( + title, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + screenSize.x, + screenSize.y, + 0 + ); + renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_SOFTWARE); + + SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE); + SDL_RenderClear(renderer); + SDL_RenderPresent(renderer); + } + + virtual ~SdlWrapper() { + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + } + + virtual void draw(SDL_Renderer *renderer) = 0; + + virtual bool update() = 0; + + virtual bool handleEvents() = 0; + + int loop() { + while (true) { + if (!handleEvents() || !update()) + break; + draw(renderer); + SDL_Delay(1000 / fps); + } + + return 1; + } +}; diff --git a/Vec2d/Bump.h b/Vec2d/Bump.h new file mode 100644 index 0000000..a7346a3 --- /dev/null +++ b/Vec2d/Bump.h @@ -0,0 +1,13 @@ +// +// Created by love on 2024-01-19. +// + +#pragma once + +enum class BumpType { + WALL, PADDLE, BOTH +}; + +enum class PaddleDirection { + MOVING_UP, NOT_MOVING, MOVING_DOWN, NONE +}; diff --git a/Vec2d/Vec2d.h b/Vec2d/Vec2d.h new file mode 100644 index 0000000..e337beb --- /dev/null +++ b/Vec2d/Vec2d.h @@ -0,0 +1,63 @@ +// +// Created by love on 2024-01-19. +// + +#pragma once + +#include "random" +#include "ctime" +#include "cmath" + +#include "SDL_rect.h" +#include "Bump.h" + +inline double_t toRadians(double_t degrees) { + return degrees * M_PI / 100; +} + +class Vec2d { +private: + std::default_random_engine random; + const float_t hypotenuse; + double_t x, y; + +public: + Vec2d(float_t hypotenuse) : hypotenuse(hypotenuse) { + std::random_device rd; + random = std::default_random_engine(rd()); + int sign = random() % 2 == 0 ? -1 : 1; + //double_t angle = toRadians(random() % 6000 / 100 - 30); + double_t angle = 15; + x = cos(angle) * sign * hypotenuse; + y = sin(angle) * sign * hypotenuse; + } + + void applyVector(Sint16 *ox, Sint16 *oy) const { + *ox += static_cast(std::round(x)); + *oy += static_cast(std::round(y)); + } + + void bump(BumpType bumpType, PaddleDirection paddleDirection) { + 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 = rand() % 500 / 100 + 15; + degrees *= paddleDirection == PaddleDirection::MOVING_UP ? -1 : 1; + angle = toRadians(degrees); + + // Adjusting y direction based on the angle + y = sin(angle) * hypotenuse; + } + } + } + +}; \ No newline at end of file diff --git a/VisibleObjects/Ball.h b/VisibleObjects/Ball.h new file mode 100644 index 0000000..9f3634a --- /dev/null +++ b/VisibleObjects/Ball.h @@ -0,0 +1,83 @@ +// +// Created by love on 2024-01-18. +// +#pragma once + +#include "../utils.h" +#include "../Vec2d/Vec2d.h" +#include "PlayerPaddle.h" +#include +#include +#include "optional" + +class Ball { +private: + static const uint8_t RADIUS = 15; + const SDL_Point *screen; + Sint16 x, y; + Vec2d *vec2d; + static const uint32_t color = 0xCD5C5CFF; + const PlayerPaddle *leftPaddle, *rightPaddle; + +public: + explicit Ball(const SDL_Point *screen, const PlayerPaddle *leftPaddle, const PlayerPaddle *rightPaddle) { + this->screen = screen; + this->x = screen->x / 2; + this->y = screen->y / 2; + this->vec2d = new Vec2d(2.5); + this->leftPaddle = leftPaddle; + this->rightPaddle = rightPaddle; + } + + void draw(SDL_Renderer *renderer) const { + filledCircleColor(renderer, x, y, RADIUS, color); + } + + void update() { + std::cout << "Ball x: " << x << ", y: " << y << std::endl; + std::optional paddleSide = collidedPaddle(); + bool screenEdgeVertical = collidedScreenEdgeVertical(); + if (screenEdgeVertical && paddleSide.has_value()) { + vec2d->bump(BumpType::BOTH, PaddleDirection::NONE); + } else if (screenEdgeVertical) { + vec2d->bump(BumpType::WALL, PaddleDirection::NONE); + } else if (collidedScreenEdgeHorizontal()) { + std::cout << "Player won" << std::endl; + exit(1); + } + if (paddleSide.has_value()) { + const PlayerPaddle *paddle = paddleSide.value() == Side::LEFT ? leftPaddle : rightPaddle; + vec2d->bump(BumpType::PADDLE, paddle->getPaddleDirection()); + } + vec2d->applyVector(&x, &y); + } + +private: + bool collidedScreenEdgeVertical() { + return y - RADIUS <= 0 || y + RADIUS >= screen->y; + } + + bool collidedScreenEdgeHorizontal() { + return x + RADIUS >= screen->x || x - RADIUS <= 0; + } + + std::optional collidedPaddle() { + // Check collision with right paddle + if (x + RADIUS >= rightPaddle->x && + y >= rightPaddle->y && + y <= rightPaddle->y + rightPaddle->h) { + return Side::RIGHT; + } + // Check collision with left paddle + else if (x - RADIUS <= leftPaddle->x + leftPaddle->w && + y >= leftPaddle->y && + y <= leftPaddle->y + leftPaddle->h) { + return Side::LEFT; + } + return std::nullopt; + } + + +}; + + diff --git a/VisibleObjects/PlayerPaddle.h b/VisibleObjects/PlayerPaddle.h new file mode 100644 index 0000000..faac2d5 --- /dev/null +++ b/VisibleObjects/PlayerPaddle.h @@ -0,0 +1,79 @@ +// +// Created by love on 2024-01-19. +// + +#pragma once + + +#include "Side.h" +#include "../Vec2d/Bump.h" + +class PlayerPaddle : public SDL_Rect { +private: + static const int MOVE_PER_TICK = 5; + const SDL_Point *screen; + bool movingUp, movingDown; + uint8_t color[4]{}; + +public: + PlayerPaddle(const SDL_Point *screen, const Side side) : SDL_Rect() { + w = 20; + h = 80; + x = side == Side::LEFT ? 0 : screen->x - w; + y = (screen->y - h) / 2; + movingUp = false; + movingDown = false; + this->screen = screen; + + color[0] = 255; + color[1] = 234; + color[2] = 0; + color[3] = 255; + } + + 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) { + SDL_SetRenderDrawColor(renderer, color[0], color[1], color[2], color[3]); + SDL_RenderFillRect(renderer, this); + } + + void startMoving(bool up) { + if (up) + movingUp = true; + else movingDown = true; + } + + void stopMoving(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; + } + +private: + bool canMoveDown() { + return y + h < screen->y; + } + + bool canMoveUp() { + return y > 0; + } + +}; diff --git a/VisibleObjects/Side.h b/VisibleObjects/Side.h new file mode 100644 index 0000000..eb4aa36 --- /dev/null +++ b/VisibleObjects/Side.h @@ -0,0 +1,9 @@ +// +// Created by love on 2024-01-19. +// + +#pragma once + +enum class Side { + LEFT, RIGHT +}; diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..aac643d --- /dev/null +++ b/main.cpp @@ -0,0 +1,8 @@ +#include "Game.h" + + +int main() { + Game game(SDL_Point{1000, 600}); + + return game.loop(); +} diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..cfbc783 --- /dev/null +++ b/utils.h @@ -0,0 +1,57 @@ +// +// Created by love on 2024-01-18. +// + +#pragma once + +#include "SDL.h" + + +int roundUpToMultipleOfEight(int v) { + return (v + (8 - 1)) & -8; +} + +// https://stackoverflow.com/questions/38334081/how-to-draw-circles-arcs-and-vector-graphics-in-sdl +void DrawCircle(SDL_Renderer *renderer, SDL_Point ¢er, int radius) { + // 35 / 49 is a slightly biased approximation of 1/sqrt(2) + const int arrSize = roundUpToMultipleOfEight(radius * 8 * 35 / 49); + SDL_Point points[arrSize]; + int drawCount = 0; + + const int32_t diameter = (radius * 2); + + int32_t x = (radius - 1); + int32_t y = 0; + int32_t tx = 1; + int32_t ty = 1; + int32_t error = (tx - diameter); + + while (x >= y) { + // Each of the following renders an octant of the circle + points[drawCount + 0] = {center.x + x, center.y - y}; + points[drawCount + 1] = {center.x + x, center.y + y}; + points[drawCount + 2] = {center.x - x, center.y - y}; + points[drawCount + 3] = {center.x - x, center.y + y}; + points[drawCount + 4] = {center.x + y, center.y - x}; + points[drawCount + 5] = {center.x + y, center.y + x}; + points[drawCount + 6] = {center.x - y, center.y - x}; + points[drawCount + 7] = {center.x - y, center.y + x}; + + drawCount += 8; + + if (error <= 0) { + ++y; + error += ty; + ty += 2; + } + + if (error > 0) { + --x; + tx += 2; + error += (tx - diameter); + } + } + + SDL_RenderDrawPoints(renderer, points, drawCount); +} +