#include #include #include #include #include const int WINDOW_WIDTH = 1280; const int WINDOW_HEIGHT = 720; const float PADDLE_SPEED = 1.0f; const int PADDLE_WIDTH = 10; const int PADDLE_HEIGHT = 100; const float BALL_SPEED = 1.0f; const int BALL_WIDTH = 15; const int BALL_HEIGHT = 15; enum Buttons { PaddleOneUp = 0, PaddleOneDown, PaddleTwoUp, PaddleTwoDown, }; enum class CollisionType { None, Top, Middle, Bottom, Left, Right }; struct Contact { CollisionType type; float penetration; }; class Vec2 { public: Vec2() : x(0.0f), y(0.0f) {} Vec2(float x, float y) : x(x), y(y) {} Vec2 operator+(Vec2 const& rhs) { return Vec2(x + rhs.x, y + rhs.y); } Vec2& operator+=(Vec2 const& rhs) { x += rhs.x; y += rhs.y; return *this; } Vec2 operator*(float rhs) { return Vec2(x * rhs, y * rhs); } float x, y; }; class Paddle { public: Paddle(Vec2 position, Vec2 velocity) : position(position), velocity(velocity) { rect.x = static_cast(position.x); rect.y = static_cast(position.y); rect.w = PADDLE_WIDTH; rect.h = PADDLE_HEIGHT; } void Update(float dt) { position += velocity * dt; if (position.y < 0) { // Restrict to top of the screen position.y = 0; } else if (position.y > (WINDOW_HEIGHT - PADDLE_HEIGHT)) { // Restrict to bottom of the screen position.y = WINDOW_HEIGHT - PADDLE_HEIGHT; } } void Draw(SDL_Renderer* renderer) { rect.y = static_cast(position.y); SDL_RenderFillRect(renderer, &rect); } Vec2 position; Vec2 velocity; SDL_Rect rect{}; }; class Ball { public: Ball(Vec2 position, Vec2 velocity) : position(position), velocity(velocity) { rect.x = static_cast(position.x); rect.y = static_cast(position.y); rect.w = BALL_WIDTH; rect.h = BALL_HEIGHT; } void Update(float dt) { position += velocity * dt; } void Draw(SDL_Renderer* renderer) { rect.x = static_cast(position.x); rect.y = static_cast(position.y); SDL_RenderFillRect(renderer, &rect); } void CollideWithPaddle(Contact const& contact) { position.x += contact.penetration; velocity.x = -velocity.x; if (contact.type == CollisionType::Top) { velocity.y = -.75f * BALL_SPEED; } else if (contact.type == CollisionType::Bottom) { velocity.y = 0.75f * BALL_SPEED; } } void CollideWithWall(Contact const& contact) { if ((contact.type == CollisionType::Top) || (contact.type == CollisionType::Bottom)) { position.y += contact.penetration; velocity.y = -velocity.y; } else if (contact.type == CollisionType::Left) { position.x = WINDOW_WIDTH / 2.0f; position.y = WINDOW_HEIGHT / 2.0f; velocity.x = BALL_SPEED; velocity.y = 0.75f * BALL_SPEED; } else if (contact.type == CollisionType::Right) { position.x = WINDOW_WIDTH / 2.0f; position.y = WINDOW_HEIGHT / 2.0f; velocity.x = -BALL_SPEED; velocity.y = 0.75f * BALL_SPEED; } } Vec2 position; Vec2 velocity; SDL_Rect rect{}; }; class PlayerScore { public: PlayerScore(Vec2 position, SDL_Renderer* renderer, TTF_Font* font) : renderer(renderer), font(font) { surface = TTF_RenderText_Solid(font, "0", {0xFF, 0xFF, 0xFF, 0xFF}); texture = SDL_CreateTextureFromSurface(renderer, surface); int width, height; SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); rect.x = static_cast(position.x); rect.y = static_cast(position.y); rect.w = width; rect.h = height; } ~PlayerScore() { SDL_FreeSurface(surface); SDL_DestroyTexture(texture); } void SetScore(int score) { SDL_FreeSurface(surface); SDL_DestroyTexture(texture); surface = TTF_RenderText_Solid(font, std::to_string(score).c_str(), {0xFF, 0xFF, 0xFF, 0xFF}); texture = SDL_CreateTextureFromSurface(renderer, surface); int width, height; SDL_QueryTexture(texture, nullptr, nullptr, &width, &height); rect.w = width; rect.h = height; } void Draw() { SDL_RenderCopy(renderer, texture, nullptr, &rect); } SDL_Renderer* renderer; TTF_Font* font; SDL_Surface* surface{}; SDL_Texture* texture{}; SDL_Rect rect{}; }; Contact CheckPaddleCollision(Ball const& ball, Paddle const& paddle) { float ballLeft = ball.position.x; float ballRight = ball.position.x + BALL_WIDTH; float ballTop = ball.position.y; float ballBottom = ball.position.y + BALL_HEIGHT; float paddleLeft = paddle.position.x; float paddleRight = paddle.position.x + PADDLE_WIDTH; float paddleTop = paddle.position.y; float paddleBottom = paddle.position.y + PADDLE_HEIGHT; Contact contact{}; if (ballLeft >= paddleRight) { return contact; } if (ballRight <= paddleLeft) { return contact; } if (ballTop >= paddleBottom) { return contact; } if (ballBottom <= paddleTop) { return contact; } float paddleRangeUpper = paddleBottom - (2.0f * PADDLE_HEIGHT / 3.0f); float paddleRangeMiddle = paddleBottom - (PADDLE_HEIGHT / 3.0f); if (ball.velocity.x < 0) { // Left paddle contact.penetration = paddleRight - ballLeft; } else if (ball.velocity.x > 0) { // Right paddle contact.penetration = paddleLeft - ballRight; } if ((ballBottom > paddleTop) && (ballBottom < paddleRangeUpper)) { contact.type = CollisionType::Top; } else if ((ballBottom > paddleRangeUpper) && (ballBottom < paddleRangeMiddle)) { contact.type = CollisionType::Middle; } else { contact.type = CollisionType::Bottom; } return contact; } Contact CheckWallCollision(Ball const& ball) { float ballLeft = ball.position.x; float ballRight = ball.position.x + BALL_WIDTH; float ballTop = ball.position.y; float ballBottom = ball.position.y + BALL_HEIGHT; Contact contact{}; if (ballLeft < 0.0f) { contact.type = CollisionType::Left; } else if (ballRight > WINDOW_WIDTH) { contact.type = CollisionType::Right; } else if (ballTop < 0.0f) { contact.type = CollisionType::Top; contact.penetration = -ballTop; } else if (ballBottom > WINDOW_HEIGHT) { contact.type = CollisionType::Bottom; contact.penetration = WINDOW_HEIGHT - ballBottom; } return contact; } int main() { // Initialize SDL components SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); TTF_Init(); Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048); SDL_Window* window = SDL_CreateWindow("Pong", 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0); // Initialize the font TTF_Font* scoreFont = TTF_OpenFont("DejaVuSansMono.ttf", 40); // Initialize sound effects Mix_Chunk* wallHitSound = Mix_LoadWAV("WallHit.wav"); Mix_Chunk* paddleHitSound = Mix_LoadWAV("PaddleHit.wav"); // Game logic { // Create the player score text fields PlayerScore playerOneScoreText(Vec2(WINDOW_WIDTH / 4, 20), renderer, scoreFont); PlayerScore playerTwoScoreText(Vec2(3 * WINDOW_WIDTH / 4, 20), renderer, scoreFont); // Create the ball Ball ball( Vec2(WINDOW_WIDTH / 2.0f, WINDOW_HEIGHT / 2.0f), Vec2(BALL_SPEED, 0.0f)); // Create the paddles Paddle paddleOne( Vec2(50.0f, WINDOW_HEIGHT / 2.0f), Vec2(0.0f, 0.0f)); Paddle paddleTwo( Vec2(WINDOW_WIDTH - 50.0f, WINDOW_HEIGHT / 2.0f), Vec2(0.0f, 0.0f)); int playerOneScore = 0; int playerTwoScore = 0; bool running = true; bool buttons[4] = {}; float dt = 0.0f; while (running) { auto startTime = std::chrono::high_resolution_clock::now(); SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { running = false; } else if (event.type == SDL_KEYDOWN) { if (event.key.keysym.sym == SDLK_ESCAPE) { running = false; } else if (event.key.keysym.sym == SDLK_w) { buttons[Buttons::PaddleOneUp] = true; } else if (event.key.keysym.sym == SDLK_s) { buttons[Buttons::PaddleOneDown] = true; } else if (event.key.keysym.sym == SDLK_UP) { buttons[Buttons::PaddleTwoUp] = true; } else if (event.key.keysym.sym == SDLK_DOWN) { buttons[Buttons::PaddleTwoDown] = true; } } else if (event.type == SDL_KEYUP) { if (event.key.keysym.sym == SDLK_w) { buttons[Buttons::PaddleOneUp] = false; } else if (event.key.keysym.sym == SDLK_s) { buttons[Buttons::PaddleOneDown] = false; } else if (event.key.keysym.sym == SDLK_UP) { buttons[Buttons::PaddleTwoUp] = false; } else if (event.key.keysym.sym == SDLK_DOWN) { buttons[Buttons::PaddleTwoDown] = false; } } } if (buttons[Buttons::PaddleOneUp]) { paddleOne.velocity.y = -PADDLE_SPEED; } else if (buttons[Buttons::PaddleOneDown]) { paddleOne.velocity.y = PADDLE_SPEED; } else { paddleOne.velocity.y = 0.0f; } if (buttons[Buttons::PaddleTwoUp]) { paddleTwo.velocity.y = -PADDLE_SPEED; } else if (buttons[Buttons::PaddleTwoDown]) { paddleTwo.velocity.y = PADDLE_SPEED; } else { paddleTwo.velocity.y = 0.0f; } // Update the paddle positions paddleOne.Update(dt); paddleTwo.Update(dt); // Update the ball position ball.Update(dt); // Check collisions if (Contact contact = CheckPaddleCollision(ball, paddleOne); contact.type != CollisionType::None) { ball.CollideWithPaddle(contact); Mix_PlayChannel(-1, paddleHitSound, 0); } else if (contact = CheckPaddleCollision(ball, paddleTwo); contact.type != CollisionType::None) { ball.CollideWithPaddle(contact); Mix_PlayChannel(-1, paddleHitSound, 0); } else if (contact = CheckWallCollision(ball); contact.type != CollisionType::None) { ball.CollideWithWall(contact); if (contact.type == CollisionType::Left) { ++playerTwoScore; playerTwoScoreText.SetScore(playerTwoScore); } else if (contact.type == CollisionType::Right) { ++playerOneScore; playerOneScoreText.SetScore(playerOneScore); } else { Mix_PlayChannel(-1, wallHitSound, 0); } } // Clear the window to black SDL_SetRenderDrawColor(renderer, 0x0, 0x0, 0x0, 0xFF); SDL_RenderClear(renderer); // Set the draw color to be white SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xFF); // Draw the net for (int y = 0; y < WINDOW_HEIGHT; ++y) { if (y % 5) { SDL_RenderDrawPoint(renderer, WINDOW_WIDTH / 2, y); } } // Draw the ball ball.Draw(renderer); // Draw the paddles paddleOne.Draw(renderer); paddleTwo.Draw(renderer); // Display the scores playerOneScoreText.Draw(); playerTwoScoreText.Draw(); // Present the backbuffer SDL_RenderPresent(renderer); // Calculate frame time auto stopTime = std::chrono::high_resolution_clock::now(); dt = std::chrono::duration(stopTime - startTime).count(); } } Mix_FreeChunk(wallHitSound); Mix_FreeChunk(paddleHitSound); SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); TTF_CloseFont(scoreFont); Mix_Quit(); TTF_Quit(); SDL_Quit(); return 0; }