diff --git a/image.png b/image.png new file mode 100644 index 0000000..58b16c6 Binary files /dev/null and b/image.png differ diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index cc2ad8a..a802fe7 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -29,6 +29,221 @@ static bool FileExists(const std::wstring& path); static void StopBackgroundMusic(); static void StartBackgroundMusic(); +struct LayoutMetrics +{ + int scale; + int offsetX; + int offsetY; + int layoutWidth; + int layoutHeight; + int grid; +}; + +static LayoutMetrics GetLayoutMetrics(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; + } + + LayoutMetrics metrics = {}; + metrics.scale = scale; + metrics.layoutWidth = MulDiv(WINDOW_CLIENT_WIDTH, scale, 1000); + metrics.layoutHeight = MulDiv(WINDOW_CLIENT_HEIGHT, scale, 1000); + metrics.offsetX = (clientWidth - metrics.layoutWidth) / 2; + metrics.offsetY = 0; + metrics.grid = MulDiv(GRID, scale, 1000); + return metrics; +} + +static int ScaleValue(const LayoutMetrics& metrics, int value) +{ + return MulDiv(value, metrics.scale, 1000); +} + +static int ScaleXValue(const LayoutMetrics& metrics, int value) +{ + return metrics.offsetX + MulDiv(value, metrics.scale, 1000); +} + +static int ScaleYValue(const LayoutMetrics& metrics, int value) +{ + return metrics.offsetY + MulDiv(value, metrics.scale, 1000); +} + +static RECT GetMenuCardRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rect = + { + ScaleXValue(metrics, 110), + ScaleYValue(metrics, 70), + ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 110), + ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 70) + }; + return rect; +} + +static RECT GetMenuOptionRect(HWND hWnd, int index) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT menuCard = GetMenuCardRect(hWnd); + int top = menuCard.top + ScaleValue(metrics, 140) + index * ScaleValue(metrics, 130); + RECT rect = + { + menuCard.left + ScaleValue(metrics, 36), + top, + menuCard.right - ScaleValue(metrics, 36), + top + ScaleValue(metrics, 104) + }; + return rect; +} + +static RECT GetRulesCardRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rect = + { + ScaleXValue(metrics, 76), + ScaleYValue(metrics, 54), + ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 76), + ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 54) + }; + return rect; +} + +static RECT GetHelpOptionRect(HWND hWnd, int index) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rulesCard = GetRulesCardRect(hWnd); + RECT contentRect = + { + rulesCard.left + ScaleValue(metrics, 36), + rulesCard.top + ScaleValue(metrics, 126), + rulesCard.right - ScaleValue(metrics, 36), + rulesCard.bottom - ScaleValue(metrics, 86) + }; + int optionHeight = ScaleValue(metrics, 100); + int optionGap = ScaleValue(metrics, 22); + int optionTop = contentRect.top + ScaleValue(metrics, 18); + RECT rect = + { + contentRect.left, + optionTop + index * (optionHeight + optionGap), + contentRect.right, + optionTop + index * (optionHeight + optionGap) + optionHeight + }; + return rect; +} + +static RECT GetHelpBackHintRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rulesCard = GetRulesCardRect(hWnd); + RECT rect = + { + rulesCard.left + ScaleValue(metrics, 36), + rulesCard.bottom - ScaleValue(metrics, 58), + rulesCard.right - ScaleValue(metrics, 36), + rulesCard.bottom - ScaleValue(metrics, 24) + }; + return rect; +} + +static RECT GetUpgradeOverlayRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rect = + { + ScaleXValue(metrics, 60), + ScaleYValue(metrics, 80), + ScaleXValue(metrics, WINDOW_CLIENT_WIDTH - 60), + ScaleYValue(metrics, WINDOW_CLIENT_HEIGHT - 80) + }; + return rect; +} + +static RECT GetUpgradeCardRect(HWND hWnd, int index) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT overlayRect = GetUpgradeOverlayRect(hWnd); + int gap = ScaleValue(metrics, 18); + int horizontalPadding = ScaleValue(metrics, 36); + int verticalTop = overlayRect.top + ScaleValue(metrics, 138); + int columnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3; + if (columnCount < 1) + { + columnCount = 1; + } + int rowCount = (upgradeUiState.optionCount + columnCount - 1) / columnCount; + if (rowCount < 1) + { + rowCount = 1; + } + int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap * (columnCount - 1)) / columnCount; + int availableHeight = overlayRect.bottom - verticalTop - ScaleValue(metrics, 72) - (rowCount - 1) * gap; + int cardHeight = availableHeight / rowCount; + int column = index % columnCount; + int row = index / columnCount; + int left = overlayRect.left + horizontalPadding + column * (cardWidth + gap); + int top = verticalTop + row * (cardHeight + gap); + RECT rect = { left, top, left + cardWidth, top + cardHeight }; + return rect; +} + +static RECT GetGameOverlayRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + int panelGap = ScaleValue(metrics, SIDE_PANEL_GAP); + int panelWidth = ScaleValue(metrics, SIDE_PANEL_WIDTH); + int boardLeft = ScaleXValue(metrics, WINDOW_PADDING) + panelWidth + panelGap; + int boardTop = ScaleYValue(metrics, WINDOW_PADDING); + int boardWidth = nGameWidth * metrics.grid; + RECT rect = + { + boardLeft + ScaleValue(metrics, 28), + boardTop + metrics.grid * 6 + ScaleValue(metrics, 10), + boardLeft + boardWidth - ScaleValue(metrics, 28), + boardTop + metrics.grid * 10 + ScaleValue(metrics, 30) + }; + return rect; +} + +static RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT overlayRect = GetGameOverlayRect(hWnd); + int gap = buttonCount == 3 ? ScaleValue(metrics, 8) : ScaleValue(metrics, 18); + int sidePadding = buttonCount == 3 ? ScaleValue(metrics, 14) : ScaleValue(metrics, 34); + int width = (overlayRect.right - overlayRect.left - sidePadding * 2 - gap * (buttonCount - 1)) / buttonCount; + int height = ScaleValue(metrics, 44); + int left = overlayRect.left + sidePadding + index * (width + gap); + int top = overlayRect.top + ScaleValue(metrics, 94); + RECT rect = { left, top, left + width, top + height }; + return rect; +} + +static RECT GetBackButtonRect(HWND hWnd) +{ + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + RECT rect = + { + ScaleXValue(metrics, 6), + ScaleYValue(metrics, 6), + ScaleXValue(metrics, 34), + ScaleYValue(metrics, 34) + }; + return rect; +} + static void ResetGameTimer(HWND hWnd) { KillTimer(hWnd, GAME_TIMER_ID); @@ -170,33 +385,18 @@ static bool FileExists(const std::wstring& path) 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); + LayoutMetrics metrics = GetLayoutMetrics(hWnd); + int size = ScaleValue(metrics, 28); if (size < 22) { size = 22; } - int marginRight = MulDiv(12, scale, 1000); + int marginRight = ScaleValue(metrics, 12); if (marginRight < 6) { marginRight = 6; } - int marginBottom = MulDiv(12, scale, 1000); + int marginBottom = ScaleValue(metrics, 12); if (marginBottom < 6) { marginBottom = 6; @@ -204,10 +404,10 @@ static RECT GetMusicButtonRect(HWND hWnd) 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 + metrics.offsetX + metrics.layoutWidth - marginRight - size, + metrics.offsetY + metrics.layoutHeight - marginBottom - size, + metrics.offsetX + metrics.layoutWidth - marginRight, + metrics.offsetY + metrics.layoutHeight - marginBottom }; return buttonRect; } @@ -640,6 +840,177 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } + if (currentScreen != SCREEN_MENU && IsPointInRect(GetBackButtonRect(hWnd), mouseX, mouseY)) + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + + if (currentScreen == SCREEN_MENU) + { + for (int i = 0; i < menuState.optionCount; i++) + { + if (!IsPointInRect(GetMenuOptionRect(hWnd, i), mouseX, mouseY)) + { + continue; + } + + menuState.selectedIndex = i; + if (i == 0) + { + StartGameWithMode(MODE_CLASSIC); + } + else if (i == 1) + { + StartGameWithMode(MODE_ROGUE); + } + else + { + OpenRulesScreen(); + } + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + break; + } + + if (currentScreen == SCREEN_RULES) + { + if (helpState.currentPage == 0) + { + for (int i = 0; i < helpState.optionCount; i++) + { + if (IsPointInRect(GetHelpOptionRect(hWnd, i), mouseX, mouseY)) + { + helpState.selectedIndex = i; + helpState.currentPage = i + 1; + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + } + if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY)) + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + } + } + else if (IsPointInRect(GetHelpBackHintRect(hWnd), mouseX, mouseY)) + { + helpState.currentPage = 0; + InvalidateRect(hWnd, nullptr, FALSE); + } + break; + } + + if (currentScreen == SCREEN_UPGRADE) + { + for (int i = 0; i < upgradeUiState.optionCount; i++) + { + if (!IsPointInRect(GetUpgradeCardRect(hWnd, i), mouseX, mouseY)) + { + continue; + } + + upgradeUiState.selectedIndex = i; + if (upgradeUiState.picksRemaining > 1) + { + bool currentlyMarked = upgradeUiState.marked[i]; + if (currentlyMarked) + { + upgradeUiState.marked[i] = false; + if (upgradeUiState.markedCount > 0) + { + upgradeUiState.markedCount--; + } + } + else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining) + { + upgradeUiState.marked[i] = true; + upgradeUiState.markedCount++; + } + + if (upgradeUiState.markedCount == upgradeUiState.picksRemaining) + { + ConfirmUpgradeSelection(); + ResetGameTimer(hWnd); + } + } + else + { + ConfirmUpgradeSelection(); + ResetGameTimer(hWnd); + } + + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + break; + } + + if (currentScreen == SCREEN_PLAYING && suspendFlag) + { + if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 2), mouseX, mouseY)) + { + suspendFlag = false; + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 2), mouseX, mouseY)) + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + } + + if (currentScreen == SCREEN_PLAYING && gameOverFlag) + { + if (reviveAvailable) + { + if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 3), mouseX, mouseY)) + { + if (PlayReviveVideo(hWnd)) + { + ReviveAfterVideo(); + ResetGameTimer(hWnd); + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 3), mouseX, mouseY)) + { + StartGameWithMode(currentMode); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (IsPointInRect(GetOverlayButtonRect(hWnd, 2, 3), mouseX, mouseY)) + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + } + else + { + if (IsPointInRect(GetOverlayButtonRect(hWnd, 0, 2), mouseX, mouseY)) + { + StartGameWithMode(currentMode); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + if (IsPointInRect(GetOverlayButtonRect(hWnd, 1, 2), mouseX, mouseY)) + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + break; + } + } + } + return DefWindowProc(hWnd, message, wParam, lParam); } case WM_KEYDOWN: diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 631c1b4..835af4f 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -374,6 +374,33 @@ void TDrawScreen(HDC hdc, HWND hWnd) DeleteObject(musicPen); }; + auto DrawBackButton = [&]() + { + RECT backButtonRect = + { + SX(6), + SY(6), + SX(34), + SY(34) + }; + DrawPanelCardAlpha( + backButtonRect, + RGB(255, 242, 247), + RGB(222, 130, 166), + 12, + 214); + + HPEN backPen = CreatePen(PS_SOLID, SS(3), RGB(128, 70, 100)); + HPEN oldBackPen = (HPEN)SelectObject(hdc, backPen); + MoveToEx(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(8), nullptr); + LineTo(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14)); + LineTo(hdc, backButtonRect.left + SS(20), backButtonRect.top + SS(20)); + MoveToEx(hdc, backButtonRect.left + SS(10), backButtonRect.top + SS(14), nullptr); + LineTo(hdc, backButtonRect.right - SS(8), backButtonRect.top + SS(14)); + SelectObject(hdc, oldBackPen); + DeleteObject(backPen); + }; + if (currentScreen == SCREEN_MENU) { RECT menuCard = @@ -746,6 +773,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) : _T("Esc / Backspace / M \u8fd4\u56de\u5e2e\u52a9"); DrawText(hdc, helpHint, -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawBackButton(); DrawMusicButton(); SelectObject(hdc, oldFont); DeleteObject(titleFont); @@ -1767,10 +1795,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) { RECT overlayRect = { - gameRect.left + SS(48), - gameRect.top + grid * 7, - gameRect.right - SS(48), - gameRect.top + grid * 11 + SS(18) + gameRect.left + SS(28), + gameRect.top + grid * 6 + SS(10), + gameRect.right - SS(28), + gameRect.top + grid * 10 + SS(30) }; HBRUSH overlayBrush = CreateSolidBrush(RGB(255, 241, 246)); @@ -1805,21 +1833,88 @@ void TDrawScreen(HDC hdc, HWND hWnd) if (suspendFlag) { DrawText(hdc, _T("时间静止"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + int buttonGap = SS(18); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; + RECT continueRect = + { + overlayRect.left + SS(34), + overlayRect.top + SS(94), + overlayRect.left + SS(34) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT menuRect = + { + continueRect.right + buttonGap, + continueRect.top, + overlayRect.right - SS(34), + continueRect.bottom + }; + DrawPanelCardAlpha(continueRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); SelectObject(hdc, bodyFont); - DrawText(hdc, _T("按 P 继续战斗"), -1, &tipRect, DT_CENTER | DT_VCENTER | DT_WORDBREAK); + DrawText(hdc, _T("继续 (P)"), -1, &continueRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } else { DrawText(hdc, _T("战局崩塌"), -1, &titleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); - SelectObject(hdc, bodyFont); - DrawText( - hdc, - reviveAvailable - ? _T("按 V 看视频复活(仅一次)\r\n按 R 重新挑战 或按 M 返回菜单") - : _T("按 R 重新挑战\r\n或按 M 返回菜单"), - -1, - &tipRect, - DT_CENTER | DT_VCENTER | DT_WORDBREAK); + if (reviveAvailable) + { + int buttonGap = SS(8); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(28) - buttonGap * 2) / 3; + RECT reviveRect = + { + overlayRect.left + SS(14), + overlayRect.top + SS(94), + overlayRect.left + SS(14) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT restartRect = + { + reviveRect.right + buttonGap, + reviveRect.top, + reviveRect.right + buttonGap + buttonWidth, + reviveRect.bottom + }; + RECT menuRect = + { + restartRect.right + buttonGap, + reviveRect.top, + overlayRect.right - SS(14), + reviveRect.bottom + }; + DrawPanelCardAlpha(reviveRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(restartRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("复活 (V)"), -1, &reviveRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + else + { + int buttonGap = SS(18); + int buttonWidth = (overlayRect.right - overlayRect.left - SS(68) - buttonGap) / 2; + RECT restartRect = + { + overlayRect.left + SS(34), + overlayRect.top + SS(94), + overlayRect.left + SS(34) + buttonWidth, + overlayRect.top + SS(138) + }; + RECT menuRect = + { + restartRect.right + buttonGap, + restartRect.top, + overlayRect.right - SS(34), + restartRect.bottom + }; + DrawPanelCardAlpha(restartRect, RGB(255, 232, 240), RGB(222, 130, 166), 18, 224); + DrawPanelCardAlpha(menuRect, RGB(255, 247, 250), RGB(210, 182, 194), 18, 210); + SelectObject(hdc, bodyFont); + DrawText(hdc, _T("重开 (R)"), -1, &restartRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("主菜单 (M)"), -1, &menuRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } } } @@ -2075,6 +2170,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) DT_CENTER | DT_VCENTER | DT_SINGLELINE); } + if (currentScreen != SCREEN_MENU) + { + DrawBackButton(); + } DrawMusicButton(); SelectObject(hdc, oldFont);