/*
 * 조이스틱 틱택토 게임 - 통합 코드 (Non-blocking)
 * - 하드웨어: Arduino Nano, OLED 디스플레이(SSD1306), 조이스틱, 부저
 * - 연결:
 *   - OLED: A4(SDA), A5(SCL)
 *   - 조이스틱: A0(X축), A1(Y축), D2(버튼)
 *   - 부저: D3
 */
#include <U8glib.h>
#include <Wire.h>
// OLED 디스플레이 초기화
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE);
// 핀 정의
const int JOYSTICK_X = A0;  // 조이스틱 X축
const int JOYSTICK_Y = A1;  // 조이스틱 Y축
const int JOYSTICK_BTN = 3; // 조이스틱 버튼
const int BUZZER_PIN = 4;   // 부저 핀
// 게임 효과음을 위한 음계 정의
#define NOTE_C4 262 // 도
#define NOTE_D4 294 // 레
#define NOTE_E4 330 // 미
#define NOTE_F4 349 // 파
#define NOTE_G4 392 // 솔
#define NOTE_A4 440 // 라
#define NOTE_B4 494 // 시
#define NOTE_C5 523 // 높은 도
// 게임 상태 정의
enum GameState
{
    STATE_INTRO,       // 시작 화면
    STATE_PLAYER_TURN, // 플레이어 턴
    STATE_CPU_TURN,    // CPU 턴
    STATE_GAME_OVER    // 게임 종료
};
// 게임 변수
GameState currentState = STATE_INTRO;    // 현재 게임 상태
boolean playerIsX = true;                // 플레이어가 X를 사용하는지 여부
int cursorRow = 1;                       // 커서 행 위치 (0-2)
int cursorCol = 1;                       // 커서 열 위치 (0-2)
int board[3][3] = {0};                   // 게임판 상태 (0=빈칸, 1=X, 2=O)
boolean gameEnded = false;               // 게임 종료 여부
int winner = 0;                          // 승자 (0=없음, 1=X, 2=O)
unsigned long lastDisplayUpdateTime = 0; // 마지막 화면 업데이트 시간
// 타이밍 변수
unsigned long lastMoveTime = 0;    // 마지막 커서 이동 시간
unsigned long lastButtonTime = 0;  // 마지막 버튼 누름 시간
unsigned long gameStartTime = 0;   // 게임 시작 시간
unsigned long lastCpuMoveTime = 0; // 마지막 CPU 이동 시간
unsigned long soundStartTime = 0;  // 효과음 시작 시간
unsigned long stateChangeTime = 0; // 상태 변경 시간
// 소리 관련 변수
boolean isSoundPlaying = false; // 소리 재생 중 여부
int currentSoundStep = 0;       // 현재 소리 재생 단계
int soundToPlay = 0;            // 0=없음, 1=시작, 2=버튼, 3=X, 4=O, 5=승리, 6=무승부, 7=에러
// 상수 정의
const int DEBOUNCE_DELAY = 200;          // 디바운싱 딜레이 (ms)
const int DISPLAY_UPDATE_INTERVAL = 100; // 디스플레이 업데이트 주기 (ms)
const int CPU_MOVE_DELAY = 1000;         // CPU 이동 대기 시간 (ms)
// 버튼 상태 변수
boolean buttonState = HIGH;     // 현재 버튼 상태
boolean lastButtonState = HIGH; // 이전 버튼 상태
void setup()
{
    Serial.begin(9600);
    Serial.println("조이스틱 틱택토 게임 시작");
    // 핀 설정
    pinMode(JOYSTICK_X, INPUT);
    pinMode(JOYSTICK_Y, INPUT);
    pinMode(JOYSTICK_BTN, INPUT_PULLUP);
    pinMode(BUZZER_PIN, OUTPUT);
    // OLED 초기화
    u8g.setFont(u8g_font_6x10);
    u8g.setColorIndex(1);
    // 게임 시작 시간 기록
    gameStartTime = millis();
    stateChangeTime = gameStartTime;
    // 시작 효과음 재생 시작
    startSound(1); // 시작 소리 코드
    // 게임판 초기화
    resetGame();
}
void loop()
{
    unsigned long currentMillis = millis();
    // 효과음 처리 (Non-blocking)
    handleSoundEffect(currentMillis);
    // 현재 게임 상태에 따른 처리
    switch (currentState)
    {
    case STATE_INTRO:
        handleIntroState(currentMillis);
        break;
    case STATE_PLAYER_TURN:
        handleJoystickInput(currentMillis);
        break;
    case STATE_CPU_TURN:
        handleCpuTurn(currentMillis);
        break;
    case STATE_GAME_OVER:
        handleGameOverState(currentMillis);
        break;
    }
    // 화면 업데이트 (업데이트 주기에 따라)
    if (currentMillis - lastDisplayUpdateTime >= DISPLAY_UPDATE_INTERVAL)
    {
        updateDisplay();
        lastDisplayUpdateTime = currentMillis;
    }
}
// Non-blocking 소리 재생 처리
void handleSoundEffect(unsigned long currentMillis)
{
    if (!isSoundPlaying || soundToPlay == 0)
        return;
    switch (soundToPlay)
    {
    case 1: // 시작 소리
        playStartSoundNB(currentMillis);
        break;
    case 2: // 버튼 클릭 소리
        playButtonSoundNB(currentMillis);
        break;
    case 3: // X 표시 소리
        playXSoundNB(currentMillis);
        break;
    case 4: // O 표시 소리
        playOSoundNB(currentMillis);
        break;
    case 5: // 승리 소리
        playWinSoundNB(currentMillis);
        break;
    case 6: // 무승부 소리
        playDrawSoundNB(currentMillis);
        break;
    case 7: // 에러 소리
        playErrorSoundNB(currentMillis);
        break;
    }
}
// 시작 화면 상태 처리
void handleIntroState(unsigned long currentMillis)
{
    // 자동 시작 비활성화 (버튼 입력만으로 게임 시작)
    // if (currentMillis - stateChangeTime >= 2000 && !isSoundPlaying)
    // {
    //     currentState = STATE_PLAYER_TURN;
    //     stateChangeTime = currentMillis;
    // }
    // 버튼을 눌러야만 게임 시작
    boolean reading = digitalRead(JOYSTICK_BTN);
    if (reading != lastButtonState)
    {
        lastButtonTime = currentMillis;
    }
    if ((currentMillis - lastButtonTime) > DEBOUNCE_DELAY)
    {
        if (reading != buttonState)
        {
            buttonState = reading;
            if (buttonState == LOW && !isSoundPlaying)
            {
                currentState = STATE_PLAYER_TURN;
                stateChangeTime = currentMillis;
                startSound(2); // 버튼 소리 재생
            }
        }
    }
    lastButtonState = reading;
}
// 조이스틱 입력 처리 (플레이어 턴)
void handleJoystickInput(unsigned long currentMillis)
{
    // 조이스틱 값 읽기
    int xValue = analogRead(JOYSTICK_X);
    int yValue = analogRead(JOYSTICK_Y);
    // 커서 이동 처리 (디바운싱 적용)
    if (currentMillis - lastMoveTime > DEBOUNCE_DELAY)
    {
        boolean moved = false;
        // X축 (좌-우)
        if (xValue < 400)
        { // 왼쪽
            if (cursorCol > 0)
            {
                cursorCol--;
                moved = true;
            }
        }
        else if (xValue > 600)
        { // 오른쪽
            if (cursorCol < 2)
            {
                cursorCol++;
                moved = true;
            }
        }
        // Y축 (위-아래)
        if (yValue < 400)
        { // 위쪽
            if (cursorRow > 0)
            {
                cursorRow--;
                moved = true;
            }
        }
        else if (yValue > 600)
        { // 아래쪽
            if (cursorRow < 2)
            {
                cursorRow++;
                moved = true;
            }
        }
        // 커서가 움직였으면 타이머 갱신
        if (moved)
        {
            lastMoveTime = currentMillis;
            startSound(2); // 버튼 소리 재생
        }
    }
    // 버튼 입력 처리
    boolean reading = digitalRead(JOYSTICK_BTN);
    if (reading != lastButtonState)
    {
        lastButtonTime = currentMillis;
    }
    if ((currentMillis - lastButtonTime) > DEBOUNCE_DELAY)
    {
        if (reading != buttonState)
        {
            buttonState = reading;
            // 버튼이 눌러졌을 때 (LOW)
            if (buttonState == LOW)
            {
                // 빈 칸이면 말 놓기
                if (board[cursorRow][cursorCol] == 0)
                {
                    board[cursorRow][cursorCol] = playerIsX ? 1 : 2; // X 또는 O 놓기
                    startSound(playerIsX ? 3 : 4);                   // X 또는 O 소리 재생
                    // 승리 또는 무승부 체크
                    checkGameResult();
                    if (!gameEnded)
                    {
                        // CPU 턴으로 전환
                        currentState = STATE_CPU_TURN;
                        lastCpuMoveTime = currentMillis;
                    }
                    else
                    {
                        // 게임 종료
                        currentState = STATE_GAME_OVER;
                        stateChangeTime = currentMillis;
                        // 승리 또는 무승부 소리 재생
                        if (winner > 0)
                        {
                            startSound(5); // 승리 소리
                        }
                        else
                        {
                            startSound(6); // 무승부 소리
                        }
                    }
                }
                else
                {
                    // 이미 말이 있는 위치면 에러 소리
                    startSound(7); // 에러 소리
                }
            }
        }
    }
    lastButtonState = reading;
}
// CPU 턴 처리
void handleCpuTurn(unsigned long currentMillis)
{
    // CPU가 생각하는 시간 (1초) 후 이동
    if (currentMillis - lastCpuMoveTime >= CPU_MOVE_DELAY && !isSoundPlaying)
    {
        // CPU 이동 로직 (간단한 랜덤 이동)
        makeCpuMove();
        // 승리 또는 무승부 체크
        checkGameResult();
        if (!gameEnded)
        {
            // 플레이어 턴으로 전환
            currentState = STATE_PLAYER_TURN;
        }
        else
        {
            // 게임 종료
            currentState = STATE_GAME_OVER;
            stateChangeTime = currentMillis;
            // 승리 또는 무승부 소리 재생
            if (winner > 0)
            {
                startSound(5); // 승리 소리
            }
            else
            {
                startSound(6); // 무승부 소리
            }
        }
    }
}
// 게임 종료 상태 처리
void handleGameOverState(unsigned long currentMillis)
{
    // 결과를 표시한 후 최소 2초 이상 경과해야 함
    boolean resultTimeElapsed = (currentMillis - stateChangeTime >= 2000);
    // 효과음이 끝나고 2초 이상 지나거나 버튼을 누르면 새 게임 시작
    // 단, 효과음 재생 중에는 건너뛸 수 없음
    if ((resultTimeElapsed && !isSoundPlaying) ||
        (buttonState == LOW && (currentMillis - lastButtonTime) > DEBOUNCE_DELAY && !isSoundPlaying && resultTimeElapsed))
    {
        resetGame();
        currentState = STATE_PLAYER_TURN;
        stateChangeTime = currentMillis;
        startSound(1); // 시작 소리 재생
    }
    // 버튼 상태 체크
    boolean reading = digitalRead(JOYSTICK_BTN);
    if (reading != lastButtonState)
    {
        lastButtonTime = currentMillis;
    }
    if ((currentMillis - lastButtonTime) > DEBOUNCE_DELAY)
    {
        if (reading != buttonState)
        {
            buttonState = reading;
        }
    }
    lastButtonState = reading;
}
// CPU 이동 로직 (간단한 랜덤 이동)
void makeCpuMove()
{
    int cpuValue = playerIsX ? 2 : 1; // CPU가 O 또는 X 사용
    // 1. 승리할 수 있는 자리 찾기
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 0)
            {
                // 임시로 말을 놓아 승리 가능성 체크
                board[i][j] = cpuValue;
                if (checkWin(cpuValue))
                {
                    startSound(playerIsX ? 4 : 3); // O 또는 X 소리 재생
                    return;                        // 승리할 수 있는 자리 찾음
                }
                board[i][j] = 0; // 원상복구
            }
        }
    }
    // 2. 상대방이 승리할 수 있는 자리 막기
    int playerValue = playerIsX ? 1 : 2;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 0)
            {
                // 임시로 상대방 말을 놓아 승리 가능성 체크
                board[i][j] = playerValue;
                if (checkWin(playerValue))
                {
                    board[i][j] = cpuValue;        // 이 자리에 CPU 말 놓기
                    startSound(playerIsX ? 4 : 3); // O 또는 X 소리 재생
                    return;                        // 상대방 승리 방지
                }
                board[i][j] = 0; // 원상복구
            }
        }
    }
    // 3. 중앙이 비어있으면 중앙에 놓기
    if (board[1][1] == 0)
    {
        board[1][1] = cpuValue;
        startSound(playerIsX ? 4 : 3); // O 또는 X 소리 재생
        return;
    }
    // 4. 랜덤으로 빈 자리 찾기
    int emptySpots = 0;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 0)
                emptySpots++;
        }
    }
    if (emptySpots > 0)
    {
        int randomSpot = random(emptySpots);
        emptySpots = 0;
        for (int i = 0; i < 3; i++)
        {
            for (int j = 0; j < 3; j++)
            {
                if (board[i][j] == 0)
                {
                    if (emptySpots == randomSpot)
                    {
                        board[i][j] = cpuValue;
                        startSound(playerIsX ? 4 : 3); // O 또는 X 소리 재생
                        return;
                    }
                    emptySpots++;
                }
            }
        }
    }
}
// 게임 결과 체크 (승리 또는 무승부)
void checkGameResult()
{
    // X 승리 체크
    if (checkWin(1))
    {
        winner = 1;
        gameEnded = true;
        return;
    }
    // O 승리 체크
    if (checkWin(2))
    {
        winner = 2;
        gameEnded = true;
        return;
    }
    // 무승부 체크 (모든 칸이 채워진 경우)
    boolean isFull = true;
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 0)
            {
                isFull = false;
                break;
            }
        }
        if (!isFull)
            break;
    }
    if (isFull)
    {
        winner = 0; // 무승부
        gameEnded = true;
    }
}
// 승리 체크 (같은 값이 3개 연속되는지)
boolean checkWin(int player)
{
    // 가로줄 체크
    for (int i = 0; i < 3; i++)
    {
        if (board[i][0] == player && board[i][1] == player && board[i][2] == player)
        {
            return true;
        }
    }
    // 세로줄 체크
    for (int i = 0; i < 3; i++)
    {
        if (board[0][i] == player && board[1][i] == player && board[2][i] == player)
        {
            return true;
        }
    }
    // 대각선 체크 (왼쪽 위 - 오른쪽 아래)
    if (board[0][0] == player && board[1][1] == player && board[2][2] == player)
    {
        return true;
    }
    // 대각선 체크 (오른쪽 위 - 왼쪽 아래)
    if (board[0][2] == player && board[1][1] == player && board[2][0] == player)
    {
        return true;
    }
    return false;
}
// 게임 리셋
void resetGame()
{
    // 게임판 초기화
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            board[i][j] = 0;
        }
    }
    // 변수 초기화
    cursorRow = 1;
    cursorCol = 1;
    gameEnded = false;
    winner = 0;
    // 시작하는 사람 랜덤 결정 (여기서는 항상 사용자가 먼저 시작하도록 설정)
    // playerIsX = random(2) == 0;
}
// 화면 업데이트
void updateDisplay()
{
    u8g.firstPage();
    do
    {
        // 게임 상태에 따라 다른 화면 그리기
        switch (currentState)
        {
        case STATE_INTRO:
            drawIntroScreen();
            break;
        case STATE_PLAYER_TURN:
        case STATE_CPU_TURN:
            drawGameScreen();
            break;
        case STATE_GAME_OVER:
            drawGameOverScreen();
            break;
        }
    } while (u8g.nextPage());
}
// 시작 화면 그리기
void drawIntroScreen()
{
    u8g.setFont(u8g_font_6x10);
    u8g.drawStr(28, 20, "TIC TAC TOE");
    u8g.drawStr(15, 40, "PRESS BUTTON TO");
    u8g.drawStr(40, 50, "START");
}
// 게임 화면 그리기
void drawGameScreen()
{
    // 게임판 그리기
    drawBoard();
    // 말 그리기 (X와 O)
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 1)
            { // X
                drawX(i, j);
            }
            else if (board[i][j] == 2)
            { // O
                drawO(i, j);
            }
        }
    }
    // 현재 플레이어 표시
    u8g.drawStr(5, 10, currentState == STATE_PLAYER_TURN ? "YOUR TURN" : "CPU TURN");
    // 플레이어가 X인지 O인지 표시
    u8g.drawStr(80, 10, playerIsX ? "YOU: X" : "YOU: O");
    // 현재 선택된 셀 표시 (플레이어 턴일 때만)
    if (currentState == STATE_PLAYER_TURN)
    {
        highlightCell(cursorRow, cursorCol);
    }
}
// 게임 종료 화면 그리기
void drawGameOverScreen()
{
    // 게임판 그리기
    drawBoard();
    // 말 그리기 (X와 O)
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            if (board[i][j] == 1)
            { // X
                drawX(i, j);
            }
            else if (board[i][j] == 2)
            { // O
                drawO(i, j);
            }
        }
    }
    // 결과 표시
    u8g.drawStr(30, 10, "GAME OVER");
    if (winner == 0)
    {
        u8g.drawStr(45, 63, "DRAW!");
    }
    else if ((winner == 1 && playerIsX) || (winner == 2 && !playerIsX))
    {
        u8g.drawStr(37, 63, "YOU WIN!");
    }
    else
    {
        u8g.drawStr(37, 63, "CPU WIN!");
    }
}
// 틱택토 게임판 그리기
void drawBoard()
{
    u8g.drawLine(42, 20, 42, 63); // 세로선 1
    u8g.drawLine(84, 20, 84, 63); // 세로선 2
    u8g.drawLine(0, 34, 128, 34); // 가로선 1
    u8g.drawLine(0, 49, 128, 49); // 가로선 2
}
// X 표시 그리기 (row, col: 0-2)
void drawX(int row, int col)
{
    int x = col * 42 + 10;
    int y = row * 15 + 25;
    u8g.drawLine(x, y, x + 20, y + 8);
    u8g.drawLine(x, y + 8, x + 20, y);
}
// O 표시 그리기 (row, col: 0-2)
void drawO(int row, int col)
{
    int x = col * 42 + 21;
    int y = row * 15 + 29;
    u8g.drawCircle(x, y, 6);
}
// 선택된 셀 하이라이트 표시
void highlightCell(int row, int col)
{
    int x = col * 42 + 1;
    int y = row * 15 + 21;
    u8g.setColorIndex(2); // XOR 모드
    u8g.drawBox(x, y, 40, 12);
    u8g.setColorIndex(1); // 일반 모드로 복원
}
// 효과음 시작
void startSound(int soundCode)
{
    soundToPlay = soundCode;
    isSoundPlaying = true;
    currentSoundStep = 0;
    soundStartTime = millis();
}
//----- Non-blocking 효과음 함수들 -----
// 시작 신호음 (Non-blocking)
void playStartSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_C4, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_E4, 100);
        currentSoundStep = 2;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 2 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_G4, 100);
        currentSoundStep = 3;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 3 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_C5, 300);
        currentSoundStep = 4;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 4 && elapsed >= 350)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// 버튼 클릭 효과음 (Non-blocking)
void playButtonSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_A4, 50);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 100)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// X 표시 효과음 (Non-blocking)
void playXSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_G4, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// O 표시 효과음 (Non-blocking)
void playOSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_E4, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// 승리 효과음 (Non-blocking)
void playWinSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_C4, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_E4, 100);
        currentSoundStep = 2;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 2 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_G4, 100);
        currentSoundStep = 3;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 3 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_C5, 300);
        currentSoundStep = 4;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 4 && elapsed >= 350)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// 무승부 효과음 (Non-blocking)
void playDrawSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_C4, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_C4, 100);
        currentSoundStep = 2;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 2 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_C4, 300);
        currentSoundStep = 3;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 3 && elapsed >= 350)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}
// 에러/잘못된 입력 효과음 (Non-blocking)
void playErrorSoundNB(unsigned long currentMillis)
{
    unsigned long elapsed = currentMillis - soundStartTime;
    if (currentSoundStep == 0 && elapsed >= 0)
    {
        tone(BUZZER_PIN, NOTE_C5, 100);
        currentSoundStep = 1;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 1 && elapsed >= 150)
    {
        tone(BUZZER_PIN, NOTE_C4, 300);
        currentSoundStep = 2;
        soundStartTime = currentMillis;
    }
    else if (currentSoundStep == 2 && elapsed >= 350)
    {
        // 효과음 종료
        noTone(BUZZER_PIN);
        isSoundPlaying = false;
        soundToPlay = 0;
    }
}