#include <U8g2lib.h>
#include <Wire.h>
// --- Pin Definitions ---
#define JOY_X_PIN A2  // Joystick X output (Use an analog pin) - Assuming Y moves paddle horizontally based on original code
#define JOY_Y_PIN A3  // Joystick Y output (Use an analog pin)
#define JOY_BTN_PIN 4 // Joystick Button Pin
#define BUZZER_PIN 11 // Buzzer connected to D11
// --- Sound Constants ---
const int NOTE_C5 = 523;
const int NOTE_D5 = 587;
const int NOTE_E5 = 659;
const int NOTE_F5 = 698;
const int NOTE_G5 = 784;
const int NOTE_A5 = 880;
const int NOTE_B5 = 988;
const int NOTE_C6 = 1047;
// --- OLED Display Setup ---
// Use Hardware I2C
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE);
// --- Screen Dimensions ---
constexpr uint8_t SCREEN_WIDTH = 128;
constexpr uint8_t SCREEN_HEIGHT = 64;
// --- Game States ---
enum GameState
{
    START_SCREEN,
    GAME_SCREEN,
    GAME_OVER_SCREEN,
    LEADERBOARD_SCREEN
};
GameState currentState = START_SCREEN;
// --- Timing ---
unsigned long lastStateChangeTime = 0;
// const unsigned long STATE_DURATION_MS = 3000; // 3 seconds - No longer needed for GAME_SCREEN transition
const unsigned long OTHER_STATE_DURATION_MS = 3000; // Duration for GameOver and Leaderboard
unsigned long buttonPressStartTime = 0;
const unsigned long LONG_PRESS_DURATION_MS = 3000; // 3 seconds for long press
// --- Start Screen Constants ---
const int16_t START_SCREEN_CENTER_X = 40;
const int16_t START_SCREEN_BRICK_Y = 20;
const uint8_t START_BRICK_WIDTH = 24;
const uint8_t START_BRICK_HEIGHT = 12;
const uint8_t START_BRICK_DEPTH_X = 6;
const uint8_t START_BRICK_DEPTH_Y = 4;
const uint8_t START_BRICK_SPACING = 4;
const uint8_t START_NUM_BRICKS = 3;
const uint8_t START_BALL_RADIUS = 4;
const uint8_t START_TEXT_Y_TITLE = 10;
const uint8_t START_TEXT_Y_PROMPT = 55;
// --- Game Screen Constants ---
constexpr uint8_t GAME_BRICK_ROWS = 2;
constexpr uint8_t GAME_BRICK_COLS = 10;
constexpr uint8_t GAME_BRICK_SPACING = 2;
constexpr uint8_t GAME_BRICK_HEIGHT = 4;
constexpr uint8_t GAME_BRICK_TOP_OFFSET = 0;
constexpr uint8_t GAME_BRICK_WIDTH = (SCREEN_WIDTH - (GAME_BRICK_COLS - 1) * GAME_BRICK_SPACING) / GAME_BRICK_COLS;
constexpr uint8_t GAME_PADDLE_WIDTH = 20; // Changed to match reference code's paddle width
constexpr uint8_t GAME_PADDLE_HEIGHT = 4;
constexpr uint8_t GAME_PADDLE_MARGIN = 1; // bottom margin
constexpr uint8_t GAME_BALL_RADIUS = 3;
constexpr uint8_t GAME_BALL_PADDLE_GAP = 6;
// Paddle movement constants matching 04_조이스틱_패들_잔상처리.ino
const int PADDLE_DEADZONE = 50;
const int PADDLE_SPEED = 5;    // max speed (renamed to match reference)
float paddleVel = 0.0;         // current velocity
const float ACCEL_RATE = 0.32; // acceleration per update (renamed to match reference)
// Motion Blur settings
const int MAX_BLUR_STEPS = 4;           // Maximum number of trail steps (renamed to match reference)
const float TRAIL_SPACING_FACTOR = 0.8; // Spacing factor for trail segments (renamed to match reference)
// Paddle position variable
int paddleX = (SCREEN_WIDTH - GAME_PADDLE_WIDTH) / 2; // Initial position centered
// --- Game Over Screen Constants ---
const u8g2_uint_t GAMEOVER_TITLE_Y = 24;
const u8g2_uint_t GAMEOVER_SUBTITLE_Y = 55;
// --- Leaderboard Screen Constants ---
uint16_t highScores[4] = {0, 0, 0, 0}; // Initialize with zeros instead of example scores
// --- Ball Movement Variables ---
float ballX, ballY;                     // Ball position (using float for smooth movement)
float ballVelX = 1.5;                   // Ball X velocity
float ballVelY = -2.0;                  // Ball Y velocity (negative means moving up)
const float BALL_SPEED_INCREASE = 0.05; // Ball speed increase after each brick hit
const float MAX_BALL_SPEED = 1.7;       // Maximum ball speed
// --- Game State Variables ---
bool ballLaunched = false;                         // Flag to indicate if the ball is in play
bool brickState[GAME_BRICK_ROWS][GAME_BRICK_COLS]; // Array to track brick state (true = active/visible)
uint16_t playerScore = 0;                          // Player's current score
const uint8_t BRICK_SCORE = 5;                     // Score earned per brick
const uint8_t LEVEL_SCORE = 50;                    // Score for completing a level
bool gameStarted = false;                          // Flag to indicate if game has started
uint8_t playerLives = 5;                           // Player starts with 5 lives
bool showLifeLostMessage = false;                  // Flag to show life lost message
unsigned long lifeLostMessageTime = 0;             // Time when life was lost (for message display)
const unsigned long LIFE_MESSAGE_DURATION = 1500;  // Show message for 1.5 seconds
bool startMusicPlayed = false;                     // Flag to track if the start music has been played
uint16_t highestScore = 0;                         // Track highest score ever achieved
// --- Function Prototypes ---
void drawStartScreen();
void drawStartBrick(int16_t x, bool filled);
void drawStartCracks(int16_t x);
void drawStartHatch(int16_t x);
void drawStartBallAtCenter();
void drawGameScreen();
void drawGameBricks();
void drawGamePaddle();
void drawGameBall();
void drawGameOverScreen();
void drawLeaderboardScreen();
void updateGamePaddle();                     // Add prototype for paddle update logic
void updateGameBall();                       // New function to update ball position and handle collisions
void checkBrickCollision(float x, float y);  // Check and handle brick collisions
void resetGame();                            // Reset game state for new game
bool checkPaddleCollision(float x, float y); // Check for paddle collision
void initBricks();                           // Initialize all bricks
// Sound functions
void playStartMusic();        // Play music at start screen
void playBounceSound();       // Sound when ball bounces
void playBrickHitSound();     // Sound when brick is hit
void playLifeLostSound();     // Sound when player loses a life
void playGameOverSound();     // Sound when game is over
void playLevelClearedSound(); // Sound when level is cleared
void playButtonSound();       // Sound when button is pressed
void setup()
{
    Serial.begin(115200); // Optional: for debugging
    Wire.begin();
    u8g2.begin();
    u8g2.setFont(u8g2_font_ncenB08_tr); // Default font
    pinMode(JOY_BTN_PIN, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT); // Set buzzer pin as output
    // Initialize Joystick pins - X pin is not used for paddle movement in this logic
    // pinMode(JOY_X_PIN, INPUT); // Analog pins don't need pinMode for input
    // pinMode(JOY_Y_PIN, INPUT);
    // Initialize game state
    initBricks();
    resetGame();      // Don't initialize highest score to the array's first value since it's now 0
    highestScore = 0; // Start with 0 as the highest score
    lastStateChangeTime = millis();                   // Initialize timer
    paddleX = (SCREEN_WIDTH - GAME_PADDLE_WIDTH) / 2; // Reset paddle position on setup
    paddleVel = 0.0;                                  // Reset paddle velocity
    startMusicPlayed = false;                         // Initialize start music flag
}
void loop()
{
    unsigned long currentTime = millis();
    bool stateChanged = false;
    bool buttonPressed = (digitalRead(JOY_BTN_PIN) == LOW);
    // --- State Logic and Transitions ---
    switch (currentState)
    {
    case START_SCREEN:
        // Play music immediately when entering the start screen
        if (!startMusicPlayed)
        {
            playStartMusic();
            startMusicPlayed = true;
            lastStateChangeTime = currentTime;
        }
        if (buttonPressed)
        {
            playButtonSound();
            currentState = GAME_SCREEN;
            stateChanged = true;
            paddleX = (SCREEN_WIDTH - GAME_PADDLE_WIDTH) / 2; // Reset paddle position
            paddleVel = 0.0;                                  // Reset paddle velocity
            delay(50);                                        // Simple debounce
        }
        // Play start music periodically
        if (currentTime - lastStateChangeTime > 5000)
        { // Play every 5 seconds
            playStartMusic();
            lastStateChangeTime = currentTime;
        }
        break;
    case GAME_SCREEN:
        updateGamePaddle(); // Update paddle position based on joystick
        // Ball movement and game logic
        if (!gameStarted)
        {
            // First time entering game screen
            initBricks();
            resetGame();
            gameStarted = true;
        }
        // Launch ball on button press if not already launched
        if (!ballLaunched && buttonPressed)
        {
            ballLaunched = true;
            delay(50); // Simple debounce
        }
        // Update ball position and check collisions if ball is in play
        if (ballLaunched)
        {
            updateGameBall();
        }
        // Check for long press to transition
        if (buttonPressed)
        {
            if (buttonPressStartTime == 0)
            { // Button just pressed
                buttonPressStartTime = currentTime;
            }
            else if (currentTime - buttonPressStartTime >= LONG_PRESS_DURATION_MS)
            {
                currentState = GAME_OVER_SCREEN;
                stateChanged = true;
                buttonPressStartTime = 0; // Reset timer
            }
        }
        else
        {
            buttonPressStartTime = 0; // Reset timer if button released
        }
        break;
    case GAME_OVER_SCREEN:
        if (currentTime - lastStateChangeTime >= OTHER_STATE_DURATION_MS)
        {
            currentState = LEADERBOARD_SCREEN;
            stateChanged = true;
        }
        break;
    case LEADERBOARD_SCREEN:
        if (currentTime - lastStateChangeTime >= OTHER_STATE_DURATION_MS)
        {
            currentState = START_SCREEN;
            stateChanged = true;
        }
        break;
    }
    if (stateChanged)
    {
        lastStateChangeTime = currentTime;
        // Ensure button timer is reset on any state change away from GAME_SCREEN
        if (currentState != GAME_SCREEN)
        {
            buttonPressStartTime = 0;
        }
    }
    // --- Drawing based on State ---
    u8g2.clearBuffer();
    switch (currentState)
    {
    case START_SCREEN:
        drawStartScreen();
        break;
    case GAME_SCREEN:
        drawGameScreen();
        break;
    case GAME_OVER_SCREEN:
        drawGameOverScreen();
        break;
    case LEADERBOARD_SCREEN:
        drawLeaderboardScreen();
        break;
    }
    u8g2.sendBuffer();
    // Small delay to prevent excessive looping if needed,
    // but state transitions are time-based or input-based.
    // delay(10);
}
// --- Drawing Functions ---
// == Start Screen Functions ==
void drawStartScreen()
{
    u8g2.setFont(u8g2_font_ncenB08_tr);
    u8g2.drawStr(0, START_TEXT_Y_TITLE, "BRICK BREAKER");
    u8g2.drawStr(0, START_TEXT_Y_PROMPT, "Press Button");
    // Draw 3D Bricks
    int16_t baseX = START_SCREEN_CENTER_X;
    // Explicitly cast to int16_t to resolve narrowing conversion warnings
    int16_t offsets[START_NUM_BRICKS] = {
        static_cast<int16_t>(baseX - (START_BRICK_WIDTH + START_BRICK_DEPTH_X + START_BRICK_SPACING)),
        static_cast<int16_t>(baseX),
        static_cast<int16_t>(baseX + (START_BRICK_WIDTH + START_BRICK_DEPTH_X + START_BRICK_SPACING))};
    for (uint8_t i = 0; i < START_NUM_BRICKS; i++)
    {
        bool isCenter = (i == 1);
        drawStartBrick(offsets[i], isCenter);
        if (isCenter)
            drawStartCracks(offsets[i]);
        else
            drawStartHatch(offsets[i]);
    }
    drawStartBallAtCenter();
}
void drawStartBrick(int16_t x, bool filled)
{
    // Front face
    if (filled)
        u8g2.drawBox(x, START_SCREEN_BRICK_Y, START_BRICK_WIDTH, START_BRICK_HEIGHT);
    else
        u8g2.drawFrame(x, START_SCREEN_BRICK_Y, START_BRICK_WIDTH, START_BRICK_HEIGHT);
    // Top face
    u8g2.drawLine(x, START_SCREEN_BRICK_Y, x + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y);
    u8g2.drawLine(x + START_BRICK_WIDTH, START_SCREEN_BRICK_Y, x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y);
    u8g2.drawLine(x + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y, x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y);
    // Side face
    u8g2.drawLine(x + START_BRICK_WIDTH, START_SCREEN_BRICK_Y, x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y);                                                                  // Corrected line
    u8g2.drawLine(x + START_BRICK_WIDTH, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT, x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT - START_BRICK_DEPTH_Y);                        // Corrected line
    u8g2.drawLine(x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y - START_BRICK_DEPTH_Y, x + START_BRICK_WIDTH + START_BRICK_DEPTH_X, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT - START_BRICK_DEPTH_Y); // Corrected line
}
void drawStartCracks(int16_t x)
{
    // Simplified crack drawing
    u8g2.drawLine(x + START_BRICK_WIDTH / 2, START_SCREEN_BRICK_Y, x + START_BRICK_WIDTH / 2, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT);
    u8g2.drawLine(x, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT / 2, x + START_BRICK_WIDTH, START_SCREEN_BRICK_Y + START_BRICK_HEIGHT / 2);
}
void drawStartHatch(int16_t x)
{
    // Simplified hatch drawing on front face
    for (int16_t i = 2; i < START_BRICK_HEIGHT; i += 4)
    {
        u8g2.drawLine(x, START_SCREEN_BRICK_Y + i, x + START_BRICK_WIDTH, START_SCREEN_BRICK_Y + i);
    }
}
void drawStartBallAtCenter()
{
    int16_t bx = START_SCREEN_CENTER_X + START_BRICK_WIDTH / 2; // Centered below middle brick
    int16_t by = START_SCREEN_BRICK_Y + START_BRICK_HEIGHT + START_BALL_RADIUS + 3;
    u8g2.drawDisc(bx, by, START_BALL_RADIUS, U8G2_DRAW_ALL);
}
// == Game Screen Functions ==
void updateGamePaddle()
{
    // Non-blocking update logic adapted from 04_조이스틱_패들_잔상처리.ino
    // Note: This assumes a loop interval similar to the original 10ms for smooth acceleration.
    // If the main loop runs slower, acceleration might feel different.
    int joyVal = analogRead(JOY_Y_PIN); // Read Y-axis for horizontal movement (matching reference code)
    // Calculate target velocity from joystick deflection
    float targetVel = 0;
    int diff = joyVal - 512; // Assuming 512 is the center value
    if (abs(diff) > PADDLE_DEADZONE)
    {
        // Map joystick deflection to target velocity
        // Use Y-axis: Positive diff moves right (increase paddleX), negative moves left (decrease paddleX)
        float norm = (abs(diff) - PADDLE_DEADZONE) / float(512 - PADDLE_DEADZONE);
        targetVel = norm * PADDLE_SPEED * (diff > 0 ? 1 : -1);
    }
    // Accelerate/decelerate toward targetVel
    if (paddleVel < targetVel)
        paddleVel = min(paddleVel + ACCEL_RATE, targetVel);
    else if (paddleVel > targetVel)
        paddleVel = max(paddleVel - ACCEL_RATE, targetVel);
    // Update position with smooth velocity
    paddleX += paddleVel;
    // Keep paddle within screen bounds
    paddleX = constrain(paddleX, 0, SCREEN_WIDTH - GAME_PADDLE_WIDTH);
}
void drawGameScreen()
{
    // 일반 플레이 중일 때는 Score와 Life를 표시하지 않음
    // 생명 손실 메시지를 표시해야 할 때만 메시지 표시
    if (showLifeLostMessage)
    {
        // Check if we should still show the message based on time
        if (millis() - lifeLostMessageTime < LIFE_MESSAGE_DURATION)
        {
            // Draw a centered "Score" message
            u8g2.setFont(u8g2_font_ncenB08_tr);
            char scoreText[20];
            sprintf(scoreText, "Score: %u", playerScore);
            int scoreWidth = u8g2.getStrWidth(scoreText);
            u8g2.drawStr((SCREEN_WIDTH - scoreWidth) / 2, 20, scoreText);
            // Draw a centered "Remained Life" message
            const char *message = "Remained Life:";
            int msgWidth = u8g2.getStrWidth(message);
            u8g2.drawStr((SCREEN_WIDTH - msgWidth) / 2, 32, message);
            // Draw the number of remaining lives
            char numText[3];
            sprintf(numText, "%u", playerLives);
            int numWidth = u8g2.getStrWidth(numText);
            u8g2.drawStr((SCREEN_WIDTH - numWidth) / 2, 45, numText);
        }
        else
        {
            showLifeLostMessage = false; // Time's up, stop showing message
        }
    }
    // 항상 게임 요소들을 그림
    // 단, 메시지 표시 중에는 다른 게임 요소들만 표시
    drawGameBricks();
    drawGamePaddle();
    drawGameBall();
}
void drawGameBricks()
{
    for (uint8_t r = 0; r < GAME_BRICK_ROWS; ++r)
    {
        for (uint8_t c = 0; c < GAME_BRICK_COLS; ++c)
        {
            // Only draw active bricks (those that haven't been hit)
            if (brickState[r][c])
            {
                uint8_t x = c * (GAME_BRICK_WIDTH + GAME_BRICK_SPACING);
                uint8_t y = GAME_BRICK_TOP_OFFSET + r * (GAME_BRICK_HEIGHT + GAME_BRICK_SPACING);
                // Draw bricks matching the reference code style:
                // First draw the frame (outline)
                u8g2.drawFrame(x, y, GAME_BRICK_WIDTH, GAME_BRICK_HEIGHT);
                // Then draw a slightly smaller filled box inside
                u8g2.drawBox(x + 1, y + 1, GAME_BRICK_WIDTH - 2, GAME_BRICK_HEIGHT - 2);
            }
        }
    }
}
void drawGamePaddle()
{
    // Draw paddle with motion blur, matching 04_조이스틱_패들_잔상처리.ino
    // Calculate number of blur steps based on velocity magnitude
    int blurSteps = map(abs(paddleVel), 0, PADDLE_SPEED, 0, MAX_BLUR_STEPS);
    uint8_t paddleY = SCREEN_HEIGHT - GAME_PADDLE_MARGIN - GAME_PADDLE_HEIGHT;
    // Draw trail segments (motion blur)
    for (int i = 1; i <= blurSteps; ++i)
    {
        // Calculate position of the trail segment
        // Offset is opposite to the direction of velocity
        float trailX = paddleX - (paddleVel * i * TRAIL_SPACING_FACTOR);
        // Ensure trail stays within bounds
        trailX = constrain(trailX, 0, SCREEN_WIDTH - GAME_PADDLE_WIDTH);
        // Draw trail segment (using drawFrame for a hollow effect)
        u8g2.drawFrame(trailX, paddleY, GAME_PADDLE_WIDTH, GAME_PADDLE_HEIGHT);
    }
    // Draw the main paddle (solid) at the current paddleX position
    u8g2.drawBox(paddleX, paddleY, GAME_PADDLE_WIDTH, GAME_PADDLE_HEIGHT);
}
void drawGameBall()
{
    // Draw ball at its current position (using int cast for drawing)
    if (ballLaunched)
    {
        // Draw moving ball
        u8g2.drawDisc(ballX, ballY, GAME_BALL_RADIUS);
    }
    else
    {
        // Draw static ball waiting to be launched
        uint8_t x = SCREEN_WIDTH / 2;
        uint8_t y = SCREEN_HEIGHT - GAME_PADDLE_MARGIN - GAME_PADDLE_HEIGHT - GAME_BALL_PADDLE_GAP;
        u8g2.drawDisc(x, y, GAME_BALL_RADIUS);
    }
}
// == Game Over Screen Function ==
void drawGameOverScreen()
{
    u8g2.setFont(u8g2_font_ncenB24_tr); // Larger font for title
    u8g2_uint_t title_w = u8g2.getStrWidth("Ooops!");
    u8g2.drawStr((SCREEN_WIDTH - title_w) / 2, GAMEOVER_TITLE_Y, "Ooops!");
    u8g2.setFont(u8g2_font_ncenB14_tr); // Medium font for subtitle
    u8g2_uint_t subtitle_w = u8g2.getStrWidth("Game Over !~");
    u8g2.drawStr((SCREEN_WIDTH - subtitle_w) / 2, GAMEOVER_SUBTITLE_Y, "Game Over !~");
}
// == Leaderboard Screen Function ==
void drawLeaderboardScreen()
{
    // Draw border
    u8g2.drawFrame(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
    // Top score title and value centered
    char buf[20];
    u8g2.setFont(u8g2_font_ncenB10_tr); // Slightly larger font
    const char *title = "TOP SCORE";
    int16_t w0 = u8g2.getStrWidth(title);
    u8g2.drawStr((SCREEN_WIDTH - w0) / 2, 14, title); // Adjusted Y
    u8g2.setFont(u8g2_font_ncenB14_tr); // Larger font for score
    // Show 0 if no scores have been set yet
    sprintf(buf, "%u", highestScore);
    int16_t w1 = u8g2.getStrWidth(buf);
    u8g2.drawStr((SCREEN_WIDTH - w1) / 2, 30, buf); // Adjusted Y
    // Recent scores header
    u8g2.setFont(u8g2_font_ncenB08_tr);
    const char *recentTitle = "RECENT SCORES:";
    u8g2.drawStr(10, 40, recentTitle);
    // Count how many non-zero scores we have
    int nonZeroScores = 0;
    for (int i = 0; i < 4; i++)
    {
        if (highScores[i] > 0)
            nonZeroScores++;
    }
    // Display message if no scores yet
    if (nonZeroScores == 0)
    {
        const char *noScores = "No scores yet";
        int16_t w2 = u8g2.getStrWidth(noScores);
        u8g2.drawStr((SCREEN_WIDTH - w2) / 2, 55, noScores);
    }
    else
    {
        // Only show non-zero scores (up to 3)
        int displayCount = 0;
        for (uint8_t i = 0; i < 4 && displayCount < 3; i++)
        {
            if (highScores[i] > 0)
            {
                sprintf(buf, "%u.", displayCount + 1);
                u8g2.drawStr(30, 50 + displayCount * 8, buf);
                sprintf(buf, "%u", highScores[i]);
                u8g2.drawStr(50, 50 + displayCount * 8, buf);
                displayCount++;
            }
        }
    }
}
// Initialize all bricks to active state
void initBricks()
{
    for (uint8_t r = 0; r < GAME_BRICK_ROWS; ++r)
    {
        for (uint8_t c = 0; c < GAME_BRICK_COLS; ++c)
        {
            brickState[r][c] = true; // Set all bricks to active
        }
    }
}
// Reset game state for a new game
void resetGame()
{
    // Reset ball position to starting position above paddle
    ballX = SCREEN_WIDTH / 2.0;
    ballY = SCREEN_HEIGHT - GAME_PADDLE_MARGIN - GAME_PADDLE_HEIGHT - GAME_BALL_PADDLE_GAP;
    // Reset ball velocity with a slight randomness to make each game different
    ballVelX = random(10) / 10.0 + 1.0; // Random X velocity between 1.0 and 2.0
    if (random(2) == 0)
        ballVelX = -ballVelX; // Randomly go left or right
    ballVelY = -2.0;          // Initial Y velocity (negative = upward)
    // Reset game state
    ballLaunched = false;
    // Only reset player lives and score when starting a new game from scratch
    if (!gameStarted)
    {
        playerScore = 0;
        playerLives = 5;
    }
}
// Update ball position and handle all collisions
void updateGameBall()
{
    // Update ball position
    ballX += ballVelX;
    ballY += ballVelY;
    // Check wall collisions
    // Left and right walls
    if (ballX <= GAME_BALL_RADIUS || ballX >= SCREEN_WIDTH - GAME_BALL_RADIUS)
    {
        ballVelX = -ballVelX; // Reverse X direction
        // Adjust position to prevent sticking to wall
        if (ballX < GAME_BALL_RADIUS)
            ballX = GAME_BALL_RADIUS;
        if (ballX > SCREEN_WIDTH - GAME_BALL_RADIUS)
            ballX = SCREEN_WIDTH - GAME_BALL_RADIUS;
    }
    // Top wall
    if (ballY <= GAME_BALL_RADIUS)
    {
        ballVelY = -ballVelY;     // Reverse Y direction
        ballY = GAME_BALL_RADIUS; // Adjust position to prevent sticking
    } // Bottom (game over condition)
    if (ballY >= SCREEN_HEIGHT)
    {
        // Ball went past the paddle - lose a life
        playerLives--;
        playLifeLostSound(); // Add this line
        // Set flag to show life lost message
        showLifeLostMessage = true;
        lifeLostMessageTime = millis();
        // Reset ball position and state
        ballLaunched = false;
        ballX = SCREEN_WIDTH / 2.0;
        ballY = SCREEN_HEIGHT - GAME_PADDLE_MARGIN - GAME_PADDLE_HEIGHT - GAME_BALL_PADDLE_GAP;
        // If all lives are lost, go to game over screen
        if (playerLives <= 0)
        {
            // Update highest score if current score is higher
            if (playerScore > highestScore)
            {
                highestScore = playerScore;
            }
            // Only add to leaderboard if score is greater than zero
            if (playerScore > 0)
            {
                // Shift scores down
                for (int i = 3; i > 0; i--)
                {
                    highScores[i] = highScores[i - 1];
                }
                highScores[0] = playerScore; // Current score becomes the newest entry
                // Sort the scores in descending order (bubble sort for simplicity)
                for (int i = 0; i < 3; i++)
                {
                    for (int j = 0; j < 3 - i; j++)
                    {
                        if (highScores[j] < highScores[j + 1])
                        {
                            // Swap scores
                            uint16_t temp = highScores[j];
                            highScores[j] = highScores[j + 1];
                            highScores[j + 1] = temp;
                        }
                    }
                }
            }
            // Go to game over screen
            currentState = GAME_OVER_SCREEN;
            lastStateChangeTime = millis();
            gameStarted = false; // Will cause full reset on next game
            playGameOverSound(); // Add this line
        }
    }
    // Check paddle collision
    if (checkPaddleCollision(ballX, ballY))
    {
        // Ball hit the paddle, bounce upward with slight angle adjustment
        ballVelY = -abs(ballVelY); // Ensure upward movement
        // Calculate hit position relative to paddle center (value between -1.0 and 1.0)
        float hitPos = (ballX - (paddleX + GAME_PADDLE_WIDTH / 2.0)) / (GAME_PADDLE_WIDTH / 2.0);
        // Adjust X velocity based on where the ball hit the paddle
        // This makes the ball bounce at different angles depending on where it hits
        ballVelX = hitPos * 2.0; // Max horizontal velocity depends on hit position
        // Ensure minimum X velocity to prevent ball moving straight up
        if (abs(ballVelX) < 0.5)
        {
            ballVelX = (ballVelX < 0) ? -0.5 : 0.5;
        }
    }
    // Check brick collisions and update score
    checkBrickCollision(ballX, ballY);
}
// Check for collision between ball and bricks
void checkBrickCollision(float x, float y)
{
    // Calculate which brick the ball might be hitting
    // This uses the AABB (Axis-Aligned Bounding Box) collision detection
    // First see if the ball is in the brick area at all
    if (y < GAME_BRICK_ROWS * (GAME_BRICK_HEIGHT + GAME_BRICK_SPACING) + GAME_BRICK_TOP_OFFSET + GAME_BALL_RADIUS)
    {
        // Calculate the row and column of the potential brick hit
        int8_t row = (y - GAME_BRICK_TOP_OFFSET) / (GAME_BRICK_HEIGHT + GAME_BRICK_SPACING);
        int8_t col = x / (GAME_BRICK_WIDTH + GAME_BRICK_SPACING);
        // Check if calculated row/col is within valid range and brick exists
        if (row >= 0 && row < GAME_BRICK_ROWS &&
            col >= 0 && col < GAME_BRICK_COLS &&
            brickState[row][col])
        {
            // Calculate brick edges
            float brickLeft = col * (GAME_BRICK_WIDTH + GAME_BRICK_SPACING);
            float brickRight = brickLeft + GAME_BRICK_WIDTH;
            float brickTop = GAME_BRICK_TOP_OFFSET + row * (GAME_BRICK_HEIGHT + GAME_BRICK_SPACING);
            float brickBottom = brickTop + GAME_BRICK_HEIGHT;
            // Determine if collision is more from sides or top/bottom
            bool collideX = (ballVelX > 0 && ballX + GAME_BALL_RADIUS >= brickLeft && ballX < brickLeft) ||
                            (ballVelX < 0 && ballX - GAME_BALL_RADIUS <= brickRight && ballX > brickRight);
            bool collideY = (ballVelY > 0 && ballY + GAME_BALL_RADIUS >= brickTop && ballY < brickTop) ||
                            (ballVelY < 0 && ballY - GAME_BALL_RADIUS <= brickBottom && ballY > brickBottom);
            // Brick was hit, destroy it and add to score
            brickState[row][col] = false;
            playerScore += BRICK_SCORE;
            playBrickHitSound();
            // Reverse velocity based on collision direction
            if (collideX)
                ballVelX = -ballVelX;
            if (collideY)
                ballVelY = -ballVelY;
            // If neither was detected, it's likely a corner collision
            if (!collideX && !collideY)
            {
                ballVelX = -ballVelX;
                ballVelY = -ballVelY;
            }
            // Increase ball speed slightly (with cap)
            float currentSpeed = sqrt(ballVelX * ballVelX + ballVelY * ballVelY);
            if (currentSpeed < MAX_BALL_SPEED)
            {
                float speedRatio = min((currentSpeed + BALL_SPEED_INCREASE) / currentSpeed, MAX_BALL_SPEED / currentSpeed);
                ballVelX *= speedRatio;
                ballVelY *= speedRatio;
            }
            // Check if all bricks are cleared
            bool allCleared = true;
            for (uint8_t r = 0; r < GAME_BRICK_ROWS; ++r)
            {
                for (uint8_t c = 0; c < GAME_BRICK_COLS; ++c)
                {
                    if (brickState[r][c])
                    {
                        allCleared = false;
                        break;
                    }
                }
                if (!allCleared)
                    break;
            }
            // If all bricks cleared, add bonus and reset level
            if (allCleared)
            {
                playerScore += LEVEL_SCORE; // Bonus for clearing level
                initBricks();               // Reset bricks for new level
                playLevelClearedSound();    // Add this line
                // Could increase difficulty here for each level
            }
        }
    }
}
// Check for collision between ball and paddle
bool checkPaddleCollision(float x, float y)
{
    // Only check if ball is moving downward and in the right Y position
    if (ballVelY > 0 &&
        y + GAME_BALL_RADIUS >= SCREEN_HEIGHT - GAME_PADDLE_MARGIN - GAME_PADDLE_HEIGHT &&
        y < SCREEN_HEIGHT - GAME_PADDLE_MARGIN)
    {
        // Check if ball is horizontally within paddle bounds
        return (x >= paddleX && x <= paddleX + GAME_PADDLE_WIDTH);
    }
    return false;
}
// --- Sound Functions ---
void playStartMusic()
{
    // Simple melody for start screen
    int melody[] = {NOTE_C5, NOTE_E5, NOTE_G5, NOTE_C6};
    int durations[] = {100, 100, 100, 200};
    for (int i = 0; i < 4; i++)
    {
        tone(BUZZER_PIN, melody[i], durations[i]);
        delay(durations[i] + 20); // Short delay between notes
    }
    noTone(BUZZER_PIN);
}
void playButtonSound()
{
    tone(BUZZER_PIN, NOTE_C6, 50);
    delay(50);
    noTone(BUZZER_PIN);
}
void playBounceSound()
{
    tone(BUZZER_PIN, NOTE_E5, 30);
    delay(30);
    noTone(BUZZER_PIN);
}
void playBrickHitSound()
{
    tone(BUZZER_PIN, NOTE_A5, 40);
    delay(40);
    noTone(BUZZER_PIN);
}
void playLifeLostSound()
{
    // Descending tone
    for (int i = NOTE_C6; i > NOTE_C5; i -= 50)
    {
        tone(BUZZER_PIN, i, 20);
        delay(20);
    }
    noTone(BUZZER_PIN);
}
void playGameOverSound()
{
    // Game over sound
    tone(BUZZER_PIN, NOTE_G5, 100);
    delay(150);
    tone(BUZZER_PIN, NOTE_E5, 100);
    delay(150);
    tone(BUZZER_PIN, NOTE_C5, 300);
    delay(300);
    noTone(BUZZER_PIN);
}
void playLevelClearedSound()
{
    // Level clear sound (ascending)
    for (int i = 0; i < 3; i++)
    {
        tone(BUZZER_PIN, NOTE_C5 + (i * 100), 100);
        delay(120);
    }
    tone(BUZZER_PIN, NOTE_C6, 200);
    delay(200);
    noTone(BUZZER_PIN);
}