diff --git a/include/game.h b/include/game.h index 3b3c6a6..68742a9 100644 --- a/include/game.h +++ b/include/game.h @@ -21,9 +21,11 @@ enum class Difficulty { Hard }; -constexpr float CPU_SPEED_EASY = 3.0f; -constexpr float CPU_SPEED_NORMAL = 5.5f; -constexpr float CPU_SPEED_HARD = 8.0f; +constexpr float CPU_SPEED_EASY = 180.0f; +constexpr float CPU_SPEED_NORMAL = 330.0f; +constexpr float CPU_SPEED_HARD = 480.0f; +constexpr float PLAYER_SPEED = 360.0f; +constexpr float BALL_SPEED = 420.0f; // --- Base Entities --- @@ -34,7 +36,7 @@ public: GameObject(Vector2 pos, Color c) : position(pos), color(c) {} virtual ~GameObject() = default; - virtual void Update() = 0; + virtual bool Update() = 0; virtual void Draw() = 0; }; @@ -62,7 +64,7 @@ public: } } - void Update() override; + bool Update() override; void Draw() override; }; @@ -88,7 +90,7 @@ public: } } - void Update() override; + bool Update() override; void Draw() override; }; @@ -114,18 +116,21 @@ public: void Update(float ball_y) { float paddleCenter = position.y + (height / 2.0f); + float dt = GetFrameTime(); + float moveAmount = speed * dt; - if (paddleCenter > ball_y + speed) { - position.y -= speed; + if (paddleCenter > ball_y + moveAmount) { + position.y -= moveAmount; } - else if (paddleCenter < ball_y - speed) { - position.y += speed; + else if (paddleCenter < ball_y - moveAmount) { + position.y += moveAmount; } LimitMovement(); + position.x = 20.0f + 10.0f; } - void Update() override {} + bool Update() override { return false; } void LimitMovement() { if (position.y <= 20.0f) { diff --git a/src/game.cpp b/src/game.cpp index 8515244..c68fdb7 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -2,14 +2,14 @@ // --- Paddle Implementation --- -void Paddle::Update() { - float speed = 6.0f; +bool Paddle::Update() { + float dt = GetFrameTime(); if (IsKeyDown(KEY_UP)) { - position.y -= speed; + position.y -= PLAYER_SPEED * dt; } if (IsKeyDown(KEY_DOWN)) { - position.y += speed; + position.y += PLAYER_SPEED * dt; } // Limit movement @@ -19,6 +19,11 @@ void Paddle::Update() { if (position.y + height >= GetScreenHeight() - 20.0f) { position.y = GetScreenHeight() - 20.0f - height; } + + // Dynamic X position + position.x = GetScreenWidth() - 20.0f - 10.0f - width; + + return false; } void Paddle::Draw() { @@ -39,13 +44,16 @@ void Paddle::Draw() { // --- Ball Implementation --- -void Ball::Update() { - position.x += velocity.x; - position.y += velocity.y; +bool Ball::Update() { + float dt = GetFrameTime(); + position.x += velocity.x * dt; + position.y += velocity.y * dt; if (position.y + radius >= GetScreenHeight() - 20.0f || position.y - radius <= 20.0f) { velocity.y *= -1; + return true; } + return false; } void Ball::Draw() { diff --git a/src/main.cpp b/src/main.cpp index 546094d..7d23283 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,22 +16,54 @@ struct ScoreBoard }; +void ApplyResolution(int width, int height) { + int monitor = GetCurrentMonitor(); + int monitorWidth = GetMonitorWidth(monitor); + int monitorHeight = GetMonitorHeight(monitor); + + if (width >= monitorWidth || height >= monitorHeight) { + SetWindowState(FLAG_WINDOW_MAXIMIZED | FLAG_WINDOW_RESIZABLE); + } + else { + ClearWindowState(FLAG_WINDOW_MAXIMIZED); + SetWindowSize(width, height); + // Center the window on the active monitor + SetWindowPosition(monitorWidth / 2 - width / 2, monitorHeight / 2 - height / 2); + } +} + +void ApplyFramerate(int option) { + if (option == 0) { + ClearWindowState(FLAG_VSYNC_HINT); + SetTargetFPS(60); + } + else if (option == 1) { + ClearWindowState(FLAG_VSYNC_HINT); + SetTargetFPS(144); + } + else if (option == 2) { + SetWindowState(FLAG_VSYNC_HINT); + SetTargetFPS(0); + } +} + // Helper function to reset the ball -void ResetBall(Ball& ball, int screenWidth, int screenHeight) { - ball.position.x = screenWidth / 2.0f; - ball.position.y = screenHeight / 2.0f; +void ResetBall(Ball& ball) { + ball.position.x = GetScreenWidth() / 2.0f; + ball.position.y = GetScreenHeight() / 2.0f; int speed_choices[2] = { -1, 1 }; - ball.velocity.x *= speed_choices[GetRandomValue(0, 1)]; - ball.velocity.y *= speed_choices[GetRandomValue(0, 1)]; + ball.velocity.x = BALL_SPEED * speed_choices[GetRandomValue(0, 1)]; + ball.velocity.y = BALL_SPEED * speed_choices[GetRandomValue(0, 1)]; } int main() { std::cout << "Starting the game" << std::endl; - const int screen_width = 1280; - const int screen_height = 800; + int screen_width = 1280; + int screen_height = 800; InitWindow(screen_width, screen_height, "Pong Reloaded"); SetTargetFPS(60); + InitAudioDevice(); // --- Instantiate Objects using the new Constructors --- @@ -40,7 +72,7 @@ int main() { Yellow, 20.0f, "assets/textures/ball/basic_ball_5.png" ); - ball.velocity = Vector2{ 7.0f, 7.0f }; + ball.velocity = Vector2{ BALL_SPEED, BALL_SPEED }; Paddle player( Vector2{ screen_width - 20.0f - 10.0f - 25.0f, screen_height / 2.0f - 60.0f }, @@ -61,6 +93,15 @@ int main() { float sessionPlayTime = 0.0f; bool isPaused = false; bool shouldQuit = false; + + int resolutionOption = 0; // 0 = 1280x800, 1 = 1600x900, 2 = 1920x1080 + int framerateOption = 0; // 0 = 60 FPS, 1 = 144 FPS, 2 = VSync + bool isFullscreen = false; + int maxScoreOption = 0; // 0 = 5, 1 = 11, 2 = 15, 3 = 21 + int maxScore = 5; + bool sfxEnabled = true; + int selectedSettingLine = 0; // 0 = Resolution, 1 = Framerate, 2 = Screen Mode, 3 = Score Limit, 4 = Sound, 5 = Back + Menu mainMenu("PONG RELOADED", { "Singleplayer", "Multiplayer", "Settings", "Quit" }); Menu difficultyMenu("SELECT DIFFICULTY", { "Easy", "Normal", "Hard", "Back" }); ScoreBoard score; @@ -70,9 +111,17 @@ int main() { Texture2D wallsTexture = LoadTexture("assets/textures/spaces/walls.png"); Texture2D lineTexture = LoadTexture("assets/textures/hud/line.png"); + // --- Load Sound Effects --- + Sound paddleHitSound = LoadSound("assets/audio/paddle_hit.ogg"); + Sound wallHitSound = LoadSound("assets/audio/wall_hit.ogg"); + Sound scoreSound = LoadSound("assets/audio/score.ogg"); + // --- Main Game Loop --- while (WindowShouldClose() == false && !shouldQuit) { + screen_width = GetScreenWidth(); + screen_height = GetScreenHeight(); + BeginDrawing(); ClearBackground(Dark_Green); @@ -115,7 +164,7 @@ int main() { score.cpu_score = 0; sessionPlayTime = 0.0f; isPaused = false; - ResetBall(ball, screen_width, screen_height); + ResetBall(ball); currentState = GameState::Playing; } else if (selected == 3) { @@ -125,6 +174,135 @@ int main() { break; } + case GameState::Settings: + { + // Navigation + if (IsKeyPressed(KEY_UP)) { + selectedSettingLine--; + if (selectedSettingLine < 0) selectedSettingLine = 5; + } + if (IsKeyPressed(KEY_DOWN)) { + selectedSettingLine++; + if (selectedSettingLine > 5) selectedSettingLine = 0; + } + + // Option selection (LEFT/RIGHT or ENTER) + if (selectedSettingLine == 0) { + // Resolution + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) { + resolutionOption = (resolutionOption + 1) % 3; + if (resolutionOption == 0) ApplyResolution(1280, 800); + else if (resolutionOption == 1) ApplyResolution(1600, 900); + else if (resolutionOption == 2) ApplyResolution(1920, 1080); + } + else if (IsKeyPressed(KEY_LEFT)) { + resolutionOption = (resolutionOption - 1 + 3) % 3; + if (resolutionOption == 0) ApplyResolution(1280, 800); + else if (resolutionOption == 1) ApplyResolution(1600, 900); + else if (resolutionOption == 2) ApplyResolution(1920, 1080); + } + } + else if (selectedSettingLine == 1) { + // Framerate + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) { + framerateOption = (framerateOption + 1) % 3; + ApplyFramerate(framerateOption); + } + else if (IsKeyPressed(KEY_LEFT)) { + framerateOption = (framerateOption - 1 + 3) % 3; + ApplyFramerate(framerateOption); + } + } + else if (selectedSettingLine == 2) { + // Screen Mode + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) { + ToggleFullscreen(); + isFullscreen = !isFullscreen; + } + } + else if (selectedSettingLine == 3) { + // Score Limit + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_ENTER)) { + maxScoreOption = (maxScoreOption + 1) % 4; + } + else if (IsKeyPressed(KEY_LEFT)) { + maxScoreOption = (maxScoreOption - 1 + 4) % 4; + } + + if (maxScoreOption == 0) maxScore = 5; + else if (maxScoreOption == 1) maxScore = 11; + else if (maxScoreOption == 2) maxScore = 15; + else if (maxScoreOption == 3) maxScore = 21; + } + else if (selectedSettingLine == 4) { + // Sound Effects + if (IsKeyPressed(KEY_RIGHT) || IsKeyPressed(KEY_LEFT) || IsKeyPressed(KEY_ENTER)) { + sfxEnabled = !sfxEnabled; + } + } + else if (selectedSettingLine == 5) { + // Back + if (IsKeyPressed(KEY_ENTER)) { + currentState = GameState::MainMenu; + } + } + + // Draw Settings Menu + int sw = GetScreenWidth(); + int sh = GetScreenHeight(); + + // Title + int titleWidth = MeasureText("SETTINGS", 60); + DrawText("SETTINGS", sw / 2 - titleWidth / 2, sh / 4 - 40, 60, WHITE); + + // Construct resolution string + std::string resStr = "Resolution: "; + if (resolutionOption == 0) resStr += "1280x800"; + else if (resolutionOption == 1) resStr += "1600x900"; + else if (resolutionOption == 2) resStr += "1920x1080 (Windowed)"; + + // Construct framerate string + std::string fpsStr = "Framerate: "; + if (framerateOption == 0) fpsStr += "60 FPS"; + else if (framerateOption == 1) fpsStr += "144 FPS"; + else if (framerateOption == 2) fpsStr += "VSync"; + + // Construct screen mode string + std::string modeStr = "Screen Mode: "; + if (isFullscreen) modeStr += "Fullscreen"; + else modeStr += "Windowed"; + + // Construct score limit string + std::string scoreLimitStr = "Score Limit: " + std::to_string(maxScore); + + // Construct sound effects string + std::string sfxStr = "Sound Effects: "; + if (sfxEnabled) sfxStr += "ON"; + else sfxStr += "OFF"; + + // Draw option lines + Color resColor = (selectedSettingLine == 0) ? YELLOW : WHITE; + Color fpsColor = (selectedSettingLine == 1) ? YELLOW : WHITE; + Color modeColor = (selectedSettingLine == 2) ? YELLOW : WHITE; + Color scoreColor = (selectedSettingLine == 3) ? YELLOW : WHITE; + Color sfxColor = (selectedSettingLine == 4) ? YELLOW : WHITE; + Color backColor = (selectedSettingLine == 5) ? YELLOW : WHITE; + + int startY = sh / 2 - 90; + DrawText(resStr.c_str(), sw / 2 - MeasureText(resStr.c_str(), 30) / 2, startY, 30, resColor); + DrawText(fpsStr.c_str(), sw / 2 - MeasureText(fpsStr.c_str(), 30) / 2, startY + 45, 30, fpsColor); + DrawText(modeStr.c_str(), sw / 2 - MeasureText(modeStr.c_str(), 30) / 2, startY + 90, 30, modeColor); + DrawText(scoreLimitStr.c_str(), sw / 2 - MeasureText(scoreLimitStr.c_str(), 30) / 2, startY + 135, 30, scoreColor); + DrawText(sfxStr.c_str(), sw / 2 - MeasureText(sfxStr.c_str(), 30) / 2, startY + 180, 30, sfxColor); + DrawText("Back", sw / 2 - MeasureText("Back", 30) / 2, startY + 225, 30, backColor); + + // Controls helper + int helperWidth = MeasureText("Use UP/DOWN to navigate, LEFT/RIGHT to change settings, ENTER to select", 20); + DrawText("Use UP/DOWN to navigate, LEFT/RIGHT to change settings, ENTER to select", sw / 2 - helperWidth / 2, sh - 60, 20, GRAY); + + break; + } + case GameState::Playing: { // Toggle pause state with 'P' key @@ -140,34 +318,40 @@ int main() { if (!isPaused) { sessionPlayTime += GetFrameTime(); - ball.Update(); + if (ball.Update() && sfxEnabled) { + PlaySound(wallHitSound); + } player.Update(); cpu.Update(ball.position.y); if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ player.position.x, player.position.y, player.width, player.height })) { ball.velocity.x *= -1; + if (sfxEnabled) PlaySound(paddleHitSound); } if (CheckCollisionCircleRec(ball.position, ball.radius, Rectangle{ cpu.position.x, cpu.position.y, cpu.width, cpu.height })) { ball.velocity.x *= -1; + if (sfxEnabled) PlaySound(paddleHitSound); } if (ball.position.x + ball.radius >= screen_width - 20.0f) { score.cpu_score++; - if (score.cpu_score >= 5) { + if (sfxEnabled) PlaySound(scoreSound); + if (score.cpu_score >= maxScore) { currentState = GameState::GameOver; } else { - ResetBall(ball, screen_width, screen_height); + ResetBall(ball); } } if (ball.position.x - ball.radius <= 20.0f) { score.player_score++; - if (score.player_score >= 5) { + if (sfxEnabled) PlaySound(scoreSound); + if (score.player_score >= maxScore) { currentState = GameState::GameOver; } else { - ResetBall(ball, screen_width, screen_height); + ResetBall(ball); } } } @@ -177,7 +361,7 @@ int main() { DrawTexturePro( courtBackground, Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height }, - Rectangle{ 20.0f, 20.0f, 570.0f, 760.0f }, + Rectangle{ 20.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE @@ -187,7 +371,7 @@ int main() { DrawTexturePro( courtBackground, Rectangle{ 0.0f, 0.0f, (float)courtBackground.width, (float)courtBackground.height }, - Rectangle{ 690.0f, 20.0f, 570.0f, 760.0f }, + Rectangle{ (float)screen_width / 2.0f + 50.0f, 20.0f, (float)screen_width / 2.0f - 70.0f, (float)screen_height - 40.0f }, Vector2{ 0.0f, 0.0f }, 0.0f, WHITE @@ -201,7 +385,7 @@ int main() { // Tiled Center Dashed Line (line.png) int lineY = 20; - while (lineY < 780) { + while (lineY < screen_height - 20) { DrawTexture(lineTexture, screen_width / 2 - lineTexture.width / 2, lineY, WHITE); lineY += lineTexture.height; } @@ -255,9 +439,9 @@ int main() { int timeTextWidth = MeasureText(TextFormat("%02i:%02i", minutes, seconds), 32); // Draw background box to block out the center line and increase contrast - DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 }); - DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, 715, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 }); - DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, 721, 32, YELLOW); + DrawRectangle(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 15, 15, 15, 220 }); + DrawRectangleLines(screen_width / 2 - timeTextWidth / 2 - 15, screen_height - 85, timeTextWidth + 30, 44, Color{ 100, 100, 100, 255 }); + DrawText(TextFormat("%02i:%02i", minutes, seconds), screen_width / 2 - timeTextWidth / 2, screen_height - 79, 32, YELLOW); ball.Draw(); cpu.Draw(); @@ -342,6 +526,12 @@ int main() { UnloadTexture(wallsTexture); UnloadTexture(lineTexture); + UnloadSound(paddleHitSound); + UnloadSound(wallHitSound); + UnloadSound(scoreSound); + + CloseAudioDevice(); + CloseWindow(); return 0; } \ No newline at end of file