diff --git a/assets/audio/Famishin - とおりゃんせ~甘美風来 (通行歌~甘美风来) (Inst_)_L.ogg b/assets/audio/Famishin - とおりゃんせ~甘美風来 (通行歌~甘美风来) (Inst_)_L.ogg deleted file mode 100644 index 2a461cd..0000000 Binary files a/assets/audio/Famishin - とおりゃんせ~甘美風来 (通行歌~甘美风来) (Inst_)_L.ogg and /dev/null differ diff --git a/assets/audio/bgm.wav b/assets/audio/bgm.wav new file mode 100644 index 0000000..a8208aa Binary files /dev/null and b/assets/audio/bgm.wav differ diff --git a/image.png b/image.png new file mode 100644 index 0000000..6afdbeb Binary files /dev/null and b/image.png differ diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 3e2d489..2fa7de2 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -171,6 +171,7 @@ extern int tScore; extern bool gameOverFlag; extern bool suspendFlag; extern bool targetFlag; +extern bool bgmEnabled; extern int workRegion[20][10]; extern Point point; extern Point target; diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 08ae4d7..b4840df 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Tetris.h" +#include #define MAX_LOADSTRING 100 #define GAME_TIMER_ID 1 @@ -8,6 +9,11 @@ HINSTANCE hInst; TCHAR szTitle[MAX_LOADSTRING]; TCHAR szWindowClass[MAX_LOADSTRING]; +bool bgmEnabled = true; + +static bool bgmPlaying = false; +static bool bgmUsingMci = false; +static constexpr const wchar_t* kBgmAlias = L"TereisBgm"; ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); @@ -20,6 +26,229 @@ static void ResetGameTimer(HWND hWnd) SetTimer(hWnd, GAME_TIMER_ID, currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL, nullptr); } +static std::wstring BuildAssetPath(const wchar_t* relativePath) +{ + wchar_t modulePath[MAX_PATH] = {}; + GetModuleFileNameW(nullptr, modulePath, MAX_PATH); + + std::wstring basePath(modulePath); + size_t lastSlash = basePath.find_last_of(L"\\/"); + if (lastSlash != std::wstring::npos) + { + basePath.resize(lastSlash); + } + + std::wstring projectRelative = basePath + L"\\..\\..\\" + relativePath; + wchar_t fullPath[MAX_PATH] = {}; + DWORD result = GetFullPathNameW(projectRelative.c_str(), MAX_PATH, fullPath, nullptr); + if (result > 0 && result < MAX_PATH) + { + return fullPath; + } + + return projectRelative; +} + +static std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath) +{ + wchar_t currentDirectory[MAX_PATH] = {}; + DWORD length = GetCurrentDirectoryW(MAX_PATH, currentDirectory); + if (length == 0 || length >= MAX_PATH) + { + return L""; + } + + std::wstring candidate = std::wstring(currentDirectory) + L"\\" + relativePath; + wchar_t fullPath[MAX_PATH] = {}; + DWORD result = GetFullPathNameW(candidate.c_str(), MAX_PATH, fullPath, nullptr); + if (result > 0 && result < MAX_PATH) + { + return fullPath; + } + + return candidate; +} + +static bool FileExists(const std::wstring& path) +{ + DWORD attributes = GetFileAttributesW(path.c_str()); + return attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0; +} + +static RECT GetMusicButtonRect(HWND hWnd) +{ + RECT clientRect; + GetClientRect(hWnd, &clientRect); + + int clientWidth = clientRect.right - clientRect.left; + int clientHeight = clientRect.bottom - clientRect.top; + int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH); + int scaleY = MulDiv(clientHeight, 1000, WINDOW_CLIENT_HEIGHT); + int scale = (scaleX < scaleY) ? scaleX : scaleY; + if (scale < 500) + { + scale = 500; + } + + int layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000); + int offsetX = (clientWidth - layoutWidth) / 2; + int offsetY = 0; + int size = MulDiv(28, scale, 1000); + if (size < 22) + { + size = 22; + } + int marginRight = MulDiv(12, scale, 1000); + if (marginRight < 6) + { + marginRight = 6; + } + int marginBottom = MulDiv(12, scale, 1000); + if (marginBottom < 6) + { + marginBottom = 6; + } + + RECT buttonRect = + { + offsetX + layoutWidth - marginRight - size, + offsetY + MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000) - marginBottom - size, + offsetX + layoutWidth - marginRight, + offsetY + MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000) - marginBottom + }; + return buttonRect; +} + +static bool IsPointInRect(const RECT& rect, int x, int y) +{ + return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom; +} + +static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo) +{ + if (!FileExists(path)) + { + return false; + } + + mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); + + std::wstring openCommand = L"open \"" + path + L"\" "; + if (forceMpegVideo) + { + openCommand += L"type mpegvideo "; + } + openCommand += L"alias "; + openCommand += kBgmAlias; + + if (mciSendStringW(openCommand.c_str(), nullptr, 0, nullptr) != 0) + { + return false; + } + + std::wstring playCommand = std::wstring(L"play ") + kBgmAlias + L" repeat"; + if (mciSendStringW(playCommand.c_str(), nullptr, 0, nullptr) != 0) + { + mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); + return false; + } + + bgmPlaying = true; + bgmUsingMci = true; + return true; +} + +static void StopBackgroundMusic() +{ + if (bgmUsingMci) + { + mciSendStringW((std::wstring(L"stop ") + kBgmAlias).c_str(), nullptr, 0, nullptr); + mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); + } + else + { + PlaySoundW(nullptr, nullptr, 0); + } + + bgmPlaying = false; + bgmUsingMci = false; +} + +static void StartBackgroundMusic() +{ + if (!bgmEnabled || bgmPlaying) + { + return; + } + + const wchar_t* bgmWavRelativePath = L"assets\\audio\\bgm.wav"; + const std::wstring bgmWavCandidates[] = + { + BuildAssetPath(bgmWavRelativePath), + BuildWorkingDirAssetPath(bgmWavRelativePath) + }; + + for (const std::wstring& candidate : bgmWavCandidates) + { + if (FileExists(candidate) && + PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP)) + { + bgmPlaying = true; + bgmUsingMci = false; + return; + } + } + + const wchar_t* oggRelativePath = L"assets\\audio\\bgm.ogg"; + const std::wstring oggCandidates[] = + { + BuildAssetPath(oggRelativePath), + BuildWorkingDirAssetPath(oggRelativePath) + }; + + for (const std::wstring& candidate : oggCandidates) + { + if (TryPlayMciLoop(candidate, false) || TryPlayMciLoop(candidate, true)) + { + return; + } + } + + const wchar_t* fallbackWavRelativePath = L"assets\\audio\\background.wav"; + const std::wstring fallbackWavCandidates[] = + { + BuildAssetPath(fallbackWavRelativePath), + BuildWorkingDirAssetPath(fallbackWavRelativePath) + }; + + for (const std::wstring& candidate : fallbackWavCandidates) + { + if (FileExists(candidate) && + PlaySoundW(candidate.c_str(), nullptr, SND_FILENAME | SND_ASYNC | SND_LOOP)) + { + bgmPlaying = true; + bgmUsingMci = false; + return; + } + } + + bgmEnabled = false; +} + +static void ToggleBackgroundMusic(HWND hWnd) +{ + bgmEnabled = !bgmEnabled; + if (bgmEnabled) + { + StartBackgroundMusic(); + } + else + { + StopBackgroundMusic(); + } + InvalidateRect(hWnd, nullptr, FALSE); +} + int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, @@ -124,6 +353,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_CREATE: srand((unsigned int)time(nullptr)); ReturnToMainMenu(); + StartBackgroundMusic(); ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; @@ -295,6 +525,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_SIZE: InvalidateRect(hWnd, nullptr, FALSE); break; + case WM_LBUTTONUP: + { + int mouseX = static_cast(LOWORD(lParam)); + int mouseY = static_cast(HIWORD(lParam)); + RECT musicButtonRect = GetMusicButtonRect(hWnd); + if (IsPointInRect(musicButtonRect, mouseX, mouseY)) + { + ToggleBackgroundMusic(hWnd); + break; + } + + return DefWindowProc(hWnd, message, wParam, lParam); + } case WM_KEYDOWN: if (currentScreen == SCREEN_MENU) { @@ -580,6 +823,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; case WM_DESTROY: KillTimer(hWnd, GAME_TIMER_ID); + StopBackgroundMusic(); PostQuitMessage(0); break; default: diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 74963b3..ecedd69 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -302,6 +302,52 @@ void TDrawScreen(HDC hdc, HWND hWnd) DeleteObject(localAccentPen); }; + auto DrawMusicButton = [&]() + { + RECT musicButtonRect = + { + SX(WINDOW_CLIENT_WIDTH - 40), + SY(WINDOW_CLIENT_HEIGHT - 40), + SX(WINDOW_CLIENT_WIDTH - 12), + SY(WINDOW_CLIENT_HEIGHT - 12) + }; + DrawPanelCardAlpha( + musicButtonRect, + bgmEnabled ? RGB(255, 238, 246) : RGB(244, 236, 240), + bgmEnabled ? RGB(222, 130, 166) : RGB(170, 148, 158), + 12, + bgmEnabled ? 218 : 190); + + HPEN musicPen = CreatePen(PS_SOLID, SS(3), bgmEnabled ? RGB(128, 70, 100) : RGB(128, 112, 120)); + HPEN oldMusicPen = (HPEN)SelectObject(hdc, musicPen); + HBRUSH oldMusicBrush = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH)); + + int noteStemX = musicButtonRect.left + SS(17); + int noteTop = musicButtonRect.top + SS(7); + int noteBottom = musicButtonRect.bottom - SS(9); + MoveToEx(hdc, noteStemX, noteTop, nullptr); + LineTo(hdc, noteStemX, noteBottom); + MoveToEx(hdc, noteStemX, noteTop, nullptr); + LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(4)); + LineTo(hdc, musicButtonRect.right - SS(7), noteTop + SS(10)); + Ellipse( + hdc, + musicButtonRect.left + SS(7), + musicButtonRect.bottom - SS(13), + musicButtonRect.left + SS(19), + musicButtonRect.bottom - SS(4)); + + if (!bgmEnabled) + { + MoveToEx(hdc, musicButtonRect.left + SS(7), musicButtonRect.bottom - SS(7), nullptr); + LineTo(hdc, musicButtonRect.right - SS(6), musicButtonRect.top + SS(6)); + } + + SelectObject(hdc, oldMusicBrush); + SelectObject(hdc, oldMusicPen); + DeleteObject(musicPen); + }; + if (currentScreen == SCREEN_MENU) { RECT menuCard = @@ -418,6 +464,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) SetTextColor(hdc, RGB(128, 104, 118)); DrawText(hdc, _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4\uff0cEsc \u9000\u51fa"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont); @@ -557,6 +604,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) }; DrawText(hdc, _T("Esc / Backspace / M \u8fd4\u56de\u4e3b\u83dc\u5355"), -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont); @@ -1765,6 +1813,8 @@ void TDrawScreen(HDC hdc, HWND hWnd) DrawText(hdc, _T("W/A/S/D \u6216\u65b9\u5411\u952e\u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } + DrawMusicButton(); + SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont);