diff --git a/src/include/Tetris.h b/src/include/Tetris.h index fdffda0..a6e7283 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -14,8 +14,10 @@ constexpr int nHeight = 22; constexpr int WINDOW_PADDING = 28; constexpr int SIDE_PANEL_WIDTH = 320; constexpr int SIDE_PANEL_GAP = 28; +constexpr int SIDE_PANEL_HEIGHT = 760; constexpr int WINDOW_CLIENT_WIDTH = WINDOW_PADDING * 2 + nGameWidth * GRID + SIDE_PANEL_GAP + SIDE_PANEL_WIDTH; -constexpr int WINDOW_CLIENT_HEIGHT = WINDOW_PADDING * 2 + nGameHeight * GRID + 20; +constexpr int BOARD_CLIENT_HEIGHT = WINDOW_PADDING * 2 + nGameHeight * GRID + 20; +constexpr int WINDOW_CLIENT_HEIGHT = (BOARD_CLIENT_HEIGHT > SIDE_PANEL_HEIGHT) ? BOARD_CLIENT_HEIGHT : SIDE_PANEL_HEIGHT; struct Point { @@ -29,10 +31,36 @@ struct MenuState int optionCount; }; +struct PlayerStats +{ + int score; + int level; + int exp; + int requiredExp; + int totalLinesCleared; +}; + +struct UpgradeOption +{ + const TCHAR* name; + const TCHAR* category; + const TCHAR* description; +}; + +struct UpgradeUiState +{ + int selectedIndex; + int optionCount; + int pendingCount; + int totalChosenCount; + UpgradeOption options[3]; +}; + enum ScreenState { SCREEN_MENU = 0, - SCREEN_PLAYING = 1 + SCREEN_PLAYING = 1, + SCREEN_UPGRADE = 2 }; enum GameMode @@ -52,6 +80,9 @@ extern int workRegion[20][10]; extern Point point; extern Point target; extern MenuState menuState; +extern PlayerStats classicStats; +extern PlayerStats rogueStats; +extern UpgradeUiState upgradeUiState; extern int currentScreen; extern int currentMode; extern int bricks[7][4][4][4]; @@ -67,10 +98,12 @@ void Rotate(); void DropDown(); void Fixing(); void DeleteOneLine(int number); -void DeleteLines(); +int DeleteLines(); void ComputeTarget(); void Restart(); void StartGameWithMode(int mode); void ReturnToMainMenu(); +void OpenUpgradeMenu(); +void ConfirmUpgradeSelection(); void TDrawScreen(HDC hdc, HWND hWnd); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index a5a4a95..db1b9bf 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -209,6 +209,39 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } + if (currentScreen == SCREEN_UPGRADE) + { + switch (wParam) + { + case VK_LEFT: + case 'A': + upgradeUiState.selectedIndex--; + if (upgradeUiState.selectedIndex < 0) + { + upgradeUiState.selectedIndex = upgradeUiState.optionCount - 1; + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + case VK_RIGHT: + case 'D': + upgradeUiState.selectedIndex++; + if (upgradeUiState.selectedIndex >= upgradeUiState.optionCount) + { + upgradeUiState.selectedIndex = 0; + } + InvalidateRect(hWnd, nullptr, FALSE); + break; + case VK_RETURN: + case VK_SPACE: + ConfirmUpgradeSelection(); + InvalidateRect(hWnd, nullptr, FALSE); + break; + default: + break; + } + break; + } + if (wParam == 'M') { ReturnToMainMenu(); diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 2ffb25e..2e06ec3 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -12,6 +12,9 @@ int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 2 }; +PlayerStats classicStats = { 0, 1, 0, 0, 0 }; +PlayerStats rogueStats = { 0, 1, 0, 100, 0 }; +UpgradeUiState upgradeUiState = { 0, 0, 0, 0, {} }; int currentScreen = SCREEN_MENU; int currentMode = MODE_CLASSIC; @@ -143,6 +146,99 @@ static Point GetSpawnPoint(int brickType) return spawnPoint; } +static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) +{ + stats.score = 0; + stats.level = 1; + stats.exp = 0; + stats.requiredExp = useRogueRules ? 100 : 0; + stats.totalLinesCleared = 0; +} + +static int GetRogueScoreByLines(int linesCleared) +{ + switch (linesCleared) + { + case 1: return 100; + case 2: return 300; + case 3: return 500; + case 4: return 800; + default: return 0; + } +} + +static int GetRogueExpByLines(int linesCleared) +{ + switch (linesCleared) + { + case 1: return 10; + case 2: return 25; + case 3: return 45; + case 4: return 80; + default: return 0; + } +} + +static int ApplyLevelProgress(PlayerStats& stats) +{ + int levelUps = 0; + + while (stats.requiredExp > 0 && stats.exp >= stats.requiredExp) + { + stats.exp -= stats.requiredExp; + stats.level++; + stats.requiredExp = 100 + stats.level * 50; + levelUps++; + } + + return levelUps; +} + +static void FillUpgradeOptions() +{ + upgradeUiState.optionCount = 3; + upgradeUiState.selectedIndex = 0; + + upgradeUiState.options[0].name = _T("\u5206\u6570\u500d\u7387"); + upgradeUiState.options[0].category = _T("\u5f97\u5206"); + upgradeUiState.options[0].description = _T("\u6240\u6709\u5f97\u5206\u5c0f\u5e45\u63d0\u5347\u3002"); + + upgradeUiState.options[1].name = _T("\u7ecf\u9a8c\u5f3a\u5316"); + upgradeUiState.options[1].category = _T("\u6210\u957f"); + upgradeUiState.options[1].description = _T("\u540e\u7eed\u6d88\u884c\u53ef\u4ee5\u83b7\u5f97\u66f4\u591a EXP\u3002"); + + upgradeUiState.options[2].name = _T("\u6162\u901f\u4e0b\u843d"); + upgradeUiState.options[2].category = _T("\u64cd\u4f5c"); + upgradeUiState.options[2].description = _T("\u964d\u4f4e\u81ea\u7136\u4e0b\u843d\u901f\u5ea6\uff0c\u63d0\u9ad8\u5bb9\u9519\u3002"); +} + +static void ApplyLineClearResult(int linesCleared) +{ + if (linesCleared <= 0) + { + return; + } + + if (currentMode == MODE_CLASSIC) + { + classicStats.totalLinesCleared += linesCleared; + classicStats.score += linesCleared * 100; + tScore = classicStats.score; + return; + } + + rogueStats.totalLinesCleared += linesCleared; + rogueStats.score += GetRogueScoreByLines(linesCleared); + rogueStats.exp += GetRogueExpByLines(linesCleared); + upgradeUiState.pendingCount += ApplyLevelProgress(rogueStats); + tScore = rogueStats.score; + + if (upgradeUiState.pendingCount > 0) + { + OpenUpgradeMenu(); + } +} + /** * @brief 判断当前方块是否可以继续向下移动。 * @@ -439,8 +535,10 @@ void DeleteOneLine(int number) * 并将该行上方的内容整体下移。为了避免连续满行被漏检, * 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。 */ -void DeleteLines() +int DeleteLines() { + int clearedLines = 0; + for (int i = nGameHeight - 1; i >= 0; i--) { bool fullLine = true; @@ -457,10 +555,13 @@ void DeleteLines() if (fullLine) { DeleteOneLine(i); - tScore += 100; + clearedLines++; i++; } } + + ApplyLineClearResult(clearedLines); + return clearedLines; } /** @@ -506,11 +607,18 @@ void Restart() } } - tScore = 0; gameOverFlag = false; suspendFlag = false; targetFlag = true; + ResetPlayerStats(classicStats, false); + ResetPlayerStats(rogueStats, true); + upgradeUiState.selectedIndex = 0; + upgradeUiState.optionCount = 0; + upgradeUiState.pendingCount = 0; + upgradeUiState.totalChosenCount = 0; + tScore = 0; + type = rand() % 7; nType = rand() % 7; state = 0; @@ -525,6 +633,7 @@ void StartGameWithMode(int mode) currentMode = mode; currentScreen = SCREEN_PLAYING; Restart(); + tScore = (currentMode == MODE_CLASSIC) ? classicStats.score : rogueStats.score; } void ReturnToMainMenu() @@ -533,9 +642,45 @@ void ReturnToMainMenu() suspendFlag = false; gameOverFlag = false; menuState.optionCount = 2; + upgradeUiState.pendingCount = 0; if (menuState.selectedIndex < 0 || menuState.selectedIndex >= menuState.optionCount) { menuState.selectedIndex = 0; } } + +void OpenUpgradeMenu() +{ + if (currentMode != MODE_ROGUE || upgradeUiState.pendingCount <= 0) + { + return; + } + + FillUpgradeOptions(); + currentScreen = SCREEN_UPGRADE; +} + +void ConfirmUpgradeSelection() +{ + if (currentScreen != SCREEN_UPGRADE || upgradeUiState.optionCount <= 0) + { + return; + } + + upgradeUiState.totalChosenCount++; + + if (upgradeUiState.pendingCount > 0) + { + upgradeUiState.pendingCount--; + } + + if (upgradeUiState.pendingCount > 0) + { + FillUpgradeOptions(); + currentScreen = SCREEN_UPGRADE; + return; + } + + currentScreen = SCREEN_PLAYING; +} diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 965813f..aa1ead0 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -41,6 +41,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) int grid = SS(GRID); int panelGap = SS(SIDE_PANEL_GAP); int panelWidth = SS(SIDE_PANEL_WIDTH); + int panelHeight = SS(SIDE_PANEL_HEIGHT - WINDOW_PADDING * 2); int boardWidth = grid * nGameWidth; int boardHeight = grid * nGameHeight; @@ -57,7 +58,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) gameRect.right + panelGap, SY(WINDOW_PADDING), gameRect.right + panelGap + panelWidth, - SY(WINDOW_PADDING) + boardHeight + SY(WINDOW_PADDING) + panelHeight }; const COLORREF pageColor = RGB(255, 244, 248); @@ -164,7 +165,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) const TCHAR* modeDescriptions[2] = { _T("\u4fdd\u7559\u5f53\u524d\u539f\u7248\u4fc4\u7f57\u65af\u65b9\u5757\u73a9\u6cd5\uff0c\u76f4\u63a5\u5f00\u59cb\u5bf9\u5c40\u3002"), - _T("\u8fdb\u5165 Rogue \u5165\u53e3\uff0c\u5f53\u524d\u5148\u63a5\u5165\u57fa\u7840\u5bf9\u5c40\u6d41\u7a0b\u3002") + _T("\u8fdb\u5165 Rogue \u6a21\u5f0f\uff0c\u5df2\u63a5\u5165\u72ec\u7acb HUD \u4e0e\u7b49\u7ea7 / \u7ecf\u9a8c\u7ed3\u7b97\u3002") }; for (int i = 0; i < menuState.optionCount; i++) @@ -413,14 +414,45 @@ void TDrawScreen(HDC hdc, HWND hWnd) _stprintf_s(modeText, _T("\u5f53\u524d\u6a21\u5f0f %s"), currentMode == MODE_CLASSIC ? _T("\u7ecf\u5178") : _T("Rogue")); TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(140), modeText, lstrlen(modeText)); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(188), _T("\u4e0b\u4e00\u4e2a\u65b9\u5757"), lstrlen(_T("\u4e0b\u4e00\u4e2a\u65b9\u5757"))); + TCHAR linesText[64]; + int totalLines = (currentMode == MODE_CLASSIC) ? classicStats.totalLinesCleared : rogueStats.totalLinesCleared; + _stprintf_s(linesText, _T("\u7d2f\u8ba1\u6d88\u884c %d"), totalLines); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(176), linesText, lstrlen(linesText)); + + if (currentMode == MODE_ROGUE) + { + TCHAR levelText[64]; + _stprintf_s(levelText, _T("\u5f53\u524d\u7b49\u7ea7 %d"), rogueStats.level); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(212), levelText, lstrlen(levelText)); + + TCHAR expText[64]; + _stprintf_s(expText, _T("EXP %d / %d"), rogueStats.exp, rogueStats.requiredExp); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(248), expText, lstrlen(expText)); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(284), _T("\u4e09\u9009\u4e00\u5f3a\u5316\uff1a\u4e0b\u4e00\u6b65\u63a5\u5165"), lstrlen(_T("\u4e09\u9009\u4e00\u5f3a\u5316\uff1a\u4e0b\u4e00\u6b65\u63a5\u5165"))); + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + } + else + { + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(212), _T("\u7ecf\u5178\u6a21\u5f0f\u4fdd\u6301\u539f\u7248\u7b80\u5355\u8ba1\u5206"), lstrlen(_T("\u7ecf\u5178\u6a21\u5f0f\u4fdd\u6301\u539f\u7248\u7b80\u5355\u8ba1\u5206"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(240), _T("\u6682\u4e0d\u63a5\u5165\u7b49\u7ea7\u548c\u5f3a\u5316\u7cfb\u7edf"), lstrlen(_T("\u6682\u4e0d\u63a5\u5165\u7b49\u7ea7\u548c\u5f3a\u5316\u7cfb\u7edf"))); + SelectObject(hdc, sectionFont); + SetTextColor(hdc, textColor); + } + + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(320), _T("\u4e0b\u4e00\u4e2a\u65b9\u5757"), lstrlen(_T("\u4e0b\u4e00\u4e2a\u65b9\u5757"))); RECT nextCard = { panelRect.left + SS(24), - panelRect.top + SS(226), + panelRect.top + SS(358), panelRect.left + SS(24) + grid * 4 + SS(32), - panelRect.top + SS(226) + grid * 4 + SS(32) + panelRect.top + SS(358) + grid * 4 + SS(32) }; HBRUSH nextCardBrush = CreateSolidBrush(RGB(255, 238, 244)); @@ -461,14 +493,14 @@ void TDrawScreen(HDC hdc, HWND hWnd) } SelectObject(hdc, sectionFont); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(406), _T("\u64cd\u4f5c\u63d0\u793a"), lstrlen(_T("\u64cd\u4f5c\u63d0\u793a"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(538), _T("\u64cd\u4f5c\u63d0\u793a"), lstrlen(_T("\u64cd\u4f5c\u63d0\u793a"))); SelectObject(hdc, bodyFont); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(448), _T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"), lstrlen(_T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(484), _T("Space\uff1a\u5feb\u901f\u4e0b\u843d"), lstrlen(_T("Space\uff1a\u5feb\u901f\u4e0b\u843d"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(520), _T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"), lstrlen(_T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(556), _T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"), lstrlen(_T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"))); - TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(592), _T("M\uff1a\u8fd4\u56de\u83dc\u5355"), lstrlen(_T("M\uff1a\u8fd4\u56de\u83dc\u5355"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(580), _T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"), lstrlen(_T("\u65b9\u5411\u952e / WASD\uff1a\u79fb\u52a8 / \u65cb\u8f6c"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(616), _T("Space\uff1a\u5feb\u901f\u4e0b\u843d"), lstrlen(_T("Space\uff1a\u5feb\u901f\u4e0b\u843d"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(652), _T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"), lstrlen(_T("P\uff1a\u6682\u505c R\uff1a\u91cd\u65b0\u5f00\u59cb"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(688), _T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"), lstrlen(_T("G\uff1a\u663e\u793a / \u9690\u85cf\u843d\u70b9"))); + TextOut(hdc, panelRect.left + SS(24), panelRect.top + SS(724), _T("M\uff1a\u8fd4\u56de\u83dc\u5355"), lstrlen(_T("M\uff1a\u8fd4\u56de\u83dc\u5355"))); if (suspendFlag || gameOverFlag) { @@ -523,6 +555,153 @@ void TDrawScreen(HDC hdc, HWND hWnd) } } + if (currentScreen == SCREEN_UPGRADE) + { + HBRUSH dimBrush = CreateSolidBrush(RGB(246, 232, 238)); + RECT dimRect = + { + SX(20), + SY(20), + SX(WINDOW_CLIENT_WIDTH - 20), + SY(WINDOW_CLIENT_HEIGHT - 20) + }; + FillRect(hdc, &dimRect, dimBrush); + DeleteObject(dimBrush); + + RECT overlayRect = + { + SX(60), + SY(80), + SX(WINDOW_CLIENT_WIDTH - 60), + SY(WINDOW_CLIENT_HEIGHT - 80) + }; + + HBRUSH overlayBrush = CreateSolidBrush(RGB(255, 250, 252)); + HPEN overlayPen = CreatePen(PS_SOLID, SS(2), RGB(232, 170, 194)); + oldPen = (HPEN)SelectObject(hdc, overlayPen); + oldBrush = (HBRUSH)SelectObject(hdc, overlayBrush); + RoundRect(hdc, overlayRect.left, overlayRect.top, overlayRect.right, overlayRect.bottom, SS(30), SS(30)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(overlayBrush); + DeleteObject(overlayPen); + + SetTextColor(hdc, titleColor); + SelectObject(hdc, titleFont); + + RECT overlayTitleRect = + { + overlayRect.left, + overlayRect.top + SS(24), + overlayRect.right, + overlayRect.top + SS(68) + }; + DrawText(hdc, _T("\u5347\u7ea7\u9009\u62e9"), -1, &overlayTitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, textColor); + RECT overlaySubtitleRect = + { + overlayRect.left, + overlayRect.top + SS(70), + overlayRect.right, + overlayRect.top + SS(106) + }; + DrawText(hdc, _T("\u8bf7\u4ece\u4e09\u4e2a\u9009\u9879\u4e2d\u9009\u62e9\u4e00\u4e2a\u5f3a\u5316"), -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + + int gap = SS(18); + int cardWidth = (overlayRect.right - overlayRect.left - SS(72) - gap * 2) / 3; + int cardTop = overlayRect.top + SS(138); + int cardBottom = overlayRect.bottom - SS(72); + + for (int i = 0; i < upgradeUiState.optionCount; i++) + { + bool isSelected = (i == upgradeUiState.selectedIndex); + int left = overlayRect.left + SS(36) + i * (cardWidth + gap); + int right = left + cardWidth; + + RECT cardRect = + { + left, + cardTop, + right, + cardBottom + }; + + HBRUSH cardBrush = CreateSolidBrush(isSelected ? RGB(255, 235, 242) : RGB(255, 247, 250)); + HPEN cardPen = CreatePen(PS_SOLID, isSelected ? SS(3) : 1, isSelected ? accentColor : RGB(221, 197, 208)); + oldPen = (HPEN)SelectObject(hdc, cardPen); + oldBrush = (HBRUSH)SelectObject(hdc, cardBrush); + RoundRect(hdc, cardRect.left, cardRect.top, cardRect.right, cardRect.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(cardBrush); + DeleteObject(cardPen); + + RECT iconRect = + { + cardRect.left + SS(20), + cardRect.top + SS(20), + cardRect.left + SS(84), + cardRect.top + SS(84) + }; + + HBRUSH iconBrush = CreateSolidBrush(isSelected ? RGB(242, 176, 202) : RGB(233, 206, 217)); + HPEN iconPen = CreatePen(PS_SOLID, 1, RGB(255, 250, 252)); + oldPen = (HPEN)SelectObject(hdc, iconPen); + oldBrush = (HBRUSH)SelectObject(hdc, iconBrush); + RoundRect(hdc, iconRect.left, iconRect.top, iconRect.right, iconRect.bottom, SS(18), SS(18)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(iconBrush); + DeleteObject(iconPen); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(122, 95, 110)); + RECT categoryRect = + { + cardRect.left + SS(20), + cardRect.top + SS(96), + cardRect.right - SS(20), + cardRect.top + SS(122) + }; + DrawText(hdc, upgradeUiState.options[i].category, -1, &categoryRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SelectObject(hdc, sectionFont); + SetTextColor(hdc, isSelected ? titleColor : textColor); + RECT nameRect = + { + cardRect.left + SS(20), + cardRect.top + SS(126), + cardRect.right - SS(20), + cardRect.top + SS(170) + }; + DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_WORDBREAK); + + SelectObject(hdc, bodyFont); + SetTextColor(hdc, RGB(108, 86, 99)); + RECT descRect = + { + cardRect.left + SS(20), + cardRect.top + SS(182), + cardRect.right - SS(20), + cardRect.bottom - SS(30) + }; + DrawText(hdc, upgradeUiState.options[i].description, -1, &descRect, DT_LEFT | DT_WORDBREAK); + } + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(128, 104, 118)); + RECT hintRect = + { + overlayRect.left + SS(30), + overlayRect.bottom - SS(52), + overlayRect.right - SS(30), + overlayRect.bottom - SS(18) + }; + DrawText(hdc, _T("A / D \u6216\u65b9\u5411\u952e\u5207\u6362\uff0cEnter \u6216 Space \u786e\u8ba4"), -1, &hintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + SelectObject(hdc, oldFont); DeleteObject(titleFont); DeleteObject(sectionFont);