From 68967f66c63c6fe5cab996ae0fe165c37fc79040 Mon Sep 17 00:00:00 2001 From: Qi-huanye <2728290997@qq.com> Date: Sun, 26 Apr 2026 14:28:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=B8=AE=E5=8A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/include/Tetris.h | 8 ++ src/source/Tetris.cpp | 45 ++++++- src/source/TetrisLogic.cpp | 4 + src/source/TetrisRender.cpp | 262 ++++++++++++++++++++++++++---------- 4 files changed, 245 insertions(+), 74 deletions(-) diff --git a/src/include/Tetris.h b/src/include/Tetris.h index bb9ee10..49a8f1e 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -31,6 +31,13 @@ struct MenuState int optionCount; }; +struct HelpState +{ + int selectedIndex; + int optionCount; + int currentPage; +}; + struct PlayerStats { int score; @@ -207,6 +214,7 @@ extern int workRegion[20][10]; extern Point point; extern Point target; extern MenuState menuState; +extern HelpState helpState; extern PlayerStats classicStats; extern PlayerStats rogueStats; extern UpgradeUiState upgradeUiState; diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 29cbf3b..6c96cce 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -698,10 +698,53 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (wParam) { + case VK_UP: + case VK_LEFT: + case 'W': + case 'A': + if (helpState.currentPage == 0) + { + helpState.selectedIndex--; + if (helpState.selectedIndex < 0) + { + helpState.selectedIndex = helpState.optionCount - 1; + } + InvalidateRect(hWnd, nullptr, FALSE); + } + break; + case VK_DOWN: + case VK_RIGHT: + case 'S': + case 'D': + if (helpState.currentPage == 0) + { + helpState.selectedIndex++; + if (helpState.selectedIndex >= helpState.optionCount) + { + helpState.selectedIndex = 0; + } + InvalidateRect(hWnd, nullptr, FALSE); + } + break; + case VK_RETURN: + case VK_SPACE: + if (helpState.currentPage == 0) + { + helpState.currentPage = helpState.selectedIndex + 1; + InvalidateRect(hWnd, nullptr, FALSE); + } + break; case VK_ESCAPE: case VK_BACK: case 'M': - ReturnToMainMenu(); + if (helpState.currentPage == 0) + { + ReturnToMainMenu(); + } + else + { + helpState.currentPage = 0; + } InvalidateRect(hWnd, nullptr, FALSE); break; default: diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 4bd9591..9e0c55d 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -14,6 +14,7 @@ int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 2 }; +HelpState helpState = { 0, 3, 0 }; PlayerStats classicStats = { 0, 1, 0, 0, 0 }; PlayerStats rogueStats = { 0, 1, 0, 30, 0, 100, 100, 0 }; UpgradeUiState upgradeUiState = { 0, 0, 0, 0, {} }; @@ -1268,4 +1269,7 @@ void OpenRulesScreen() { currentScreen = SCREEN_RULES; suspendFlag = false; + helpState.selectedIndex = 0; + helpState.optionCount = 3; + helpState.currentPage = 0; } diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 858bb3e..9c062df 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -387,14 +387,14 @@ void TDrawScreen(HDC hdc, HWND hWnd) { _T("\u7ecf\u5178\u6a21\u5f0f"), _T("Rogue \u6a21\u5f0f"), - _T("\u6e38\u620f\u89c4\u5219") + _T("\u5e2e\u52a9") }; const TCHAR* modeDescriptions[3] = { _T("纯粹方块挑战,专注消行、堆叠与生存。"), _T("在不断升级的棋盘中收集强化,构筑本局专属流派。"), - _T("查看操作、成长、主动技能与特殊方块规则。") + _T("查看操作、模式规则与全部强化效果。") }; for (int i = 0; i < menuState.optionCount; i++) @@ -502,7 +502,20 @@ void TDrawScreen(HDC hdc, HWND hWnd) rulesCard.right - SS(36), rulesCard.top + SS(78) }; - DrawText(hdc, _T("\u6e38\u620f\u89c4\u5219"), -1, &rulesTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + const TCHAR* helpTitle = _T("\u5e2e\u52a9"); + if (helpState.currentPage == 1) + { + helpTitle = _T("\u6e38\u620f\u4ecb\u7ecd"); + } + else if (helpState.currentPage == 2) + { + helpTitle = _T("\u64cd\u4f5c\u8bf4\u660e"); + } + else if (helpState.currentPage == 3) + { + helpTitle = _T("\u5f3a\u5316\u56fe\u9274"); + } + DrawText(hdc, helpTitle, -1, &rulesTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); HPEN rulesAccentPen = CreatePen(PS_SOLID, SS(3), accentColor); oldPen = (HPEN)SelectObject(hdc, rulesAccentPen); @@ -511,87 +524,187 @@ void TDrawScreen(HDC hdc, HWND hWnd) SelectObject(hdc, oldPen); DeleteObject(rulesAccentPen); - RECT leftSection = + RECT contentRect = { rulesCard.left + SS(36), rulesCard.top + SS(126), - rulesCard.left + SS(330), - rulesCard.bottom - SS(86) - }; - - RECT rightSection = - { - rulesCard.left + SS(360), - rulesCard.top + SS(126), rulesCard.right - SS(36), rulesCard.bottom - SS(86) }; - SetTextColor(hdc, textColor); - SelectObject(hdc, sectionFont); - - RECT sectionTitle = + if (helpState.currentPage == 0) { - leftSection.left, - leftSection.top, - leftSection.right, - leftSection.top + SS(34) - }; - DrawText(hdc, _T("\u57fa\u672c\u64cd\u4f5c"), -1, §ionTitle, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + const TCHAR* optionTitles[3] = + { + _T("\u6e38\u620f\u4ecb\u7ecd"), + _T("\u64cd\u4f5c\u8bf4\u660e"), + _T("\u5f3a\u5316\u56fe\u9274") + }; + const TCHAR* optionDetails[3] = + { + _T("\u7ecf\u5178\u6a21\u5f0f\u3001Rogue \u6a21\u5f0f\u548c\u590d\u6d3b\u89c4\u5219\u6982\u89c8\u3002"), + _T("\u79fb\u52a8\u3001\u65cb\u8f6c\u3001\u786c\u964d\u3001Hold \u4e0e\u6280\u80fd\u5feb\u6377\u952e\u3002"), + _T("\u67e5\u770b Rogue \u6a21\u5f0f\u5168\u90e8\u5f3a\u5316\u7684\u7b80\u8981\u6548\u679c\u3002") + }; - SelectObject(hdc, bodyFont); - RECT leftBody = - { - leftSection.left, - leftSection.top + SS(48), - leftSection.right, - leftSection.bottom - }; - DrawText( - hdc, - _T("\u2190 / A\uff1a\u5411\u5de6\u79fb\u52a8\r\n") - _T("\u2192 / D\uff1a\u5411\u53f3\u79fb\u52a8\r\n") - _T("\u2191 / W\uff1a\u65cb\u8f6c\u65b9\u5757\r\n") - _T("\u2193 / S\uff1a\u8f6f\u964d\r\n") - _T("Space\uff1a\u786c\u964d\r\n") - _T("C / Shift\uff1a备用仓\uff08获得后\uff09\r\n") - _T("Z\uff1a黑洞奇点\uff08获得后\uff09\r\n") - _T("X\uff1a清屏炸弹\uff08获得后\uff09\r\n") - _T("V\uff1a空中换形\uff08获得后\uff09\r\n") - _T("P\uff1a\u6682\u505c / \u7ee7\u7eed\r\n") - _T("R\uff1a\u91cd\u5f00\u5f53\u524d\u5bf9\u5c40\r\n") - _T("M\uff1a\u8fd4\u56de\u4e3b\u83dc\u5355"), - -1, - &leftBody, - DT_LEFT | DT_TOP | DT_WORDBREAK); + int optionHeight = SS(100); + int optionGap = SS(22); + int optionTop = contentRect.top + SS(18); + for (int i = 0; i < helpState.optionCount; ++i) + { + RECT optionRect = + { + contentRect.left, + optionTop + i * (optionHeight + optionGap), + contentRect.right, + optionTop + i * (optionHeight + optionGap) + optionHeight + }; + bool selected = (i == helpState.selectedIndex); + COLORREF optionFill = selected ? RGB(255, 245, 249) : RGB(255, 252, 250); + COLORREF optionFrame = selected ? accentColor : frameColor; + HPEN optionPen = CreatePen(PS_SOLID, selected ? SS(2) : SS(1), optionFrame); + HBRUSH optionBrush = CreateSolidBrush(optionFill); + oldPen = (HPEN)SelectObject(hdc, optionPen); + oldBrush = (HBRUSH)SelectObject(hdc, optionBrush); + RoundRect(hdc, optionRect.left, optionRect.top, optionRect.right, optionRect.bottom, SS(24), SS(24)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(optionBrush); + DeleteObject(optionPen); - SelectObject(hdc, sectionFont); - RECT rulesSectionTitle = - { - rightSection.left, - rightSection.top, - rightSection.right, - rightSection.top + SS(34) - }; - DrawText(hdc, _T("\u6a21\u5f0f\u89c4\u5219"), -1, &rulesSectionTitle, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + SetTextColor(hdc, selected ? titleColor : textColor); + SelectObject(hdc, sectionFont); + RECT optionTitleRect = + { + optionRect.left + SS(28), + optionRect.top + SS(16), + optionRect.right - SS(28), + optionRect.top + SS(48) + }; + DrawText(hdc, optionTitles[i], -1, &optionTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); - SelectObject(hdc, bodyFont); - RECT rulesBody = + SetTextColor(hdc, RGB(112, 91, 104)); + SelectObject(hdc, bodyFont); + RECT optionDetailRect = + { + optionRect.left + SS(28), + optionRect.top + SS(52), + optionRect.right - SS(28), + optionRect.bottom - SS(14) + }; + DrawText(hdc, optionDetails[i], -1, &optionDetailRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + } + } + else { - rightSection.left, - rightSection.top + SS(48), - rightSection.right, - rightSection.bottom - }; - DrawText( - hdc, - _T("经典模式:没有成长与主动技能,只考验基础堆叠、消行和生存。\r\n\r\n") - _T("Rogue \u6a21\u5f0f\uff1a消行获得分数与 EXP。EXP 满后选择强化,逐步塑造本局能力。\r\n\r\n") - _T("强化会带来爆破、雷霆、狂热、黑洞、备用仓等能力;部分进阶强化需要先掌握对应前置。\r\n\r\n") - _T("战斗持续越久,危险等级越高,底部封锁区会逐步压缩可用空间。"), - -1, - &rulesBody, - DT_LEFT | DT_TOP | DT_WORDBREAK); + SetTextColor(hdc, textColor); + SelectObject(hdc, bodyFont); + const TCHAR* pageText = _T(""); + UINT pageFlags = DT_LEFT | DT_TOP | DT_WORDBREAK; + if (helpState.currentPage == 1) + { + pageText = + _T("经典模式:只考验堆叠、消行和生存。\r\n\r\n") + _T("Rogue:消行得分和 EXP,升级后选择强化。\r\n\r\n") + _T("战斗越久危险越高,底部封锁区会压缩空间。\r\n\r\n") + _T("死亡后可以按 V 看视频复活一次,经典模式和 Rogue 模式都支持。"); + } + else if (helpState.currentPage == 2) + { + pageText = + _T("\u2190 / A\uff1a\u5411\u5de6\u79fb\u52a8\r\n") + _T("\u2192 / D\uff1a\u5411\u53f3\u79fb\u52a8\r\n") + _T("\u2191 / W\uff1a\u65cb\u8f6c\u65b9\u5757\r\n") + _T("\u2193 / S\uff1a\u8f6f\u964d\r\n") + _T("Space\uff1a\u786c\u964d\r\n") + _T("C / Shift\uff1a备用仓\uff08获得后\uff09\r\n") + _T("Z\uff1a黑洞奇点\uff08获得后\uff09\r\n") + _T("X\uff1a清屏炸弹\uff08获得后\uff09\r\n") + _T("V\uff1a空中换形\uff08获得后\uff09\r\n") + _T("P\uff1a\u6682\u505c / \u7ee7\u7eed\r\n") + _T("R\uff1a\u91cd\u5f00\u5f53\u524d\u5bf9\u5c40\r\n") + _T("M\uff1a\u8fd4\u56de\u4e3b\u83dc\u5355\r\n") + _T("死亡后 V:看视频复活一次"); + } + else if (helpState.currentPage == 3) + { + const TCHAR* categoryNames[8] = + { + _T("基础"), + _T("保命 / 操作"), + _T("清场"), + _T("特殊方块"), + _T("进阶"), + _T("狂热 / 风险"), + _T("升级系"), + _T("构筑") + }; + const TCHAR* categoryTexts[8] = + { + _T("赏金纹章:得分+20%\r\n成长印记:EXP+25%\r\n缓坠羽翼:下落变慢\r\n连击律动:连续消行加分;先见之眼:预览+1"), + _T("最后一搏:濒死清底3行\r\n备用仓:解锁 Hold\r\n完美旋转:卡墙修正\r\n时间缓流:高堆叠减速;空中换形:V 变 I 块"), + _T("卸压清场:清最高行\r\n底线清道夫:消行充能清底\r\n清屏炸弹:X 清底5行\r\n黑洞奇点:Z 吞噬最多方块"), + _T("爆破核心:爆破块清 3x3\r\n棱镜激光:清整列\r\n十字方块:清行列\r\n彩虹方块:补齐缺口;方块改造:提高 I 块概率"), + _T("连锁火花:消行追加破坏\r\n连环炸弹:爆破扩大 5x5\r\n雷霆四消:四消额外轰击\r\n雷霆棱镜:四消额外激光"), + _T("狂热节拍:20行进狂热\r\n怒火连段:连击加倍率\r\n无尽狂热:延长狂热\r\n高压悬赏 / 豪赌四消 / 极限玩家:高风险高收益"), + _T("双重抉择:升级多选1个\r\n命运轮盘:6选2但带诅咒\r\n升级冲击波 / 进化冲击:升级清底\r\n成长核心:永久得分 / EXP +15%"), + _T("操控大师:Hold 后减速并预览+1\r\n方块风暴:接下来全 I 块\r\n稳定结构:落地补洞\r\n虚空核心:黑洞和彩虹联动;赌徒契约:强化可能翻倍或落空") + }; + + int columnGap = SS(18); + int rowGap = SS(14); + int columnWidth = (contentRect.right - contentRect.left - columnGap) / 2; + int rowHeight = (contentRect.bottom - contentRect.top - rowGap * 3) / 4; + for (int i = 0; i < 8; ++i) + { + int column = i % 2; + int row = i / 2; + RECT categoryRect = + { + contentRect.left + column * (columnWidth + columnGap), + contentRect.top + row * (rowHeight + rowGap), + contentRect.left + column * (columnWidth + columnGap) + columnWidth, + contentRect.top + row * (rowHeight + rowGap) + rowHeight + }; + + HPEN categoryPen = CreatePen(PS_SOLID, 1, RGB(232, 202, 215)); + HBRUSH categoryBrush = CreateSolidBrush(RGB(255, 252, 250)); + oldPen = (HPEN)SelectObject(hdc, categoryPen); + oldBrush = (HBRUSH)SelectObject(hdc, categoryBrush); + RoundRect(hdc, categoryRect.left, categoryRect.top, categoryRect.right, categoryRect.bottom, SS(16), SS(16)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(categoryBrush); + DeleteObject(categoryPen); + + SetTextColor(hdc, titleColor); + SelectObject(hdc, bodyFont); + RECT categoryTitleRect = + { + categoryRect.left + SS(16), + categoryRect.top + SS(10), + categoryRect.right - SS(16), + categoryRect.top + SS(34) + }; + DrawText(hdc, categoryNames[i], -1, &categoryTitleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + + SetTextColor(hdc, RGB(86, 66, 80)); + SelectObject(hdc, smallFont); + RECT categoryBodyRect = + { + categoryRect.left + SS(16), + categoryRect.top + SS(40), + categoryRect.right - SS(16), + categoryRect.bottom - SS(10) + }; + DrawText(hdc, categoryTexts[i], -1, &categoryBodyRect, DT_LEFT | DT_TOP | DT_WORDBREAK); + } + } + if (helpState.currentPage != 3) + { + DrawText(hdc, pageText, -1, &contentRect, pageFlags); + } + } SelectObject(hdc, smallFont); SetTextColor(hdc, RGB(128, 104, 118)); @@ -602,7 +715,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) rulesCard.right - SS(36), rulesCard.bottom - SS(24) }; - DrawText(hdc, _T("Esc / Backspace / M \u8fd4\u56de\u4e3b\u83dc\u5355"), -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + const TCHAR* helpHint = helpState.currentPage == 0 + ? _T("\u65b9\u5411\u952e / WASD \u5207\u6362\uff0cEnter / Space \u786e\u8ba4\uff0cEsc / M \u8fd4\u56de\u4e3b\u83dc\u5355") + : _T("Esc / Backspace / M \u8fd4\u56de\u5e2e\u52a9"); + DrawText(hdc, helpHint, -1, &backHintRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); DrawMusicButton(); SelectObject(hdc, oldFont);