From a331162349546cb1a728143a6d2d2bfa7c847a31 Mon Sep 17 00:00:00 2001 From: qihuanye <2728290997@qq.com> Date: Wed, 29 Apr 2026 15:21:14 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E4=B8=80=E6=AD=A5=E6=8B=86=E5=88=86?= =?UTF-8?q?=E4=B8=8E=E6=B3=A8=E9=87=8A=E8=A1=A5=E5=BC=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .worktrees/ack-syc-swj | 1 + src/source/TetrisLogic.cpp | 336 ++++++++----- src/source/app/TetrisInput.cpp | 483 ++++++++++++------- src/source/rogue/TetrisRogue.cpp | 794 ++++++++++++++++++------------- 4 files changed, 973 insertions(+), 641 deletions(-) create mode 160000 .worktrees/ack-syc-swj diff --git a/.worktrees/ack-syc-swj b/.worktrees/ack-syc-swj new file mode 160000 index 0000000..58ab400 --- /dev/null +++ b/.worktrees/ack-syc-swj @@ -0,0 +1 @@ +Subproject commit 58ab40094998453cb433060ebb0a1e92913e8d9f diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index e44de81..ce7e7a4 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -381,6 +381,129 @@ void DropDown() } } +/** + * @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。 + * @param overflowTop 返回是否有方块格位于可视区域顶部之外。 + * @param fixedCells 返回普通落地格,用于后续特殊效果定位。 + * @param fixedCellCount 返回普通落地格数量。 + * @param explosiveCells 返回爆破方块落地格。 + * @param explosiveCellCount 返回爆破方块落地格数量。 + */ +static void CollectAndWriteFixedCells( + bool& overflowTop, + Point fixedCells[], + int& fixedCellCount, + Point explosiveCells[], + int& explosiveCellCount) +{ + overflowTop = false; + fixedCellCount = 0; + explosiveCellCount = 0; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[type][state][i][j] == 0) + { + continue; + } + + int fixY = point.y + i; + int fixX = point.x + j; + + // 顶部溢出只记录状态,真正的复活或结束逻辑在后续统一处理。 + if (fixY < 0) + { + overflowTop = true; + } + + if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth) + { + workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; + if (fixedCellCount < 4) + { + fixedCells[fixedCellCount].x = fixX; + fixedCells[fixedCellCount].y = fixY; + fixedCellCount++; + } + if (currentPieceIsExplosive && explosiveCellCount < 4) + { + explosiveCells[explosiveCellCount].x = fixX; + explosiveCells[explosiveCellCount].y = fixY; + explosiveCellCount++; + } + } + } + } +} + +/** + * @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。 + * @param overflowTop 是否出现顶部溢出。 + * @return 溢出已被处理且游戏可以继续时返回 true;需要结束游戏时返回 false。 + */ +static bool ResolveFixingOverflow(bool overflowTop) +{ + if (!overflowTop) + { + return true; + } + + if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) + { + rogueStats.lastChanceCount--; + rogueStats.screenBombCount--; + + int clearedByTerminal = TriggerScreenBomb(); + rogueStats.feverTicks = 10; + currentFallInterval = GetRogueFallInterval(); + + TCHAR terminalDetail[128]; + _stprintf_s( + terminalDetail, + _T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"), + clearedByTerminal); + SetFeedbackMessage(_T("终末清场"), terminalDetail, 14); + return true; + } + + if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0) + { + rogueStats.lastChanceCount--; + + for (int i = 0; i < 3; i++) + { + DeleteOneLine(GetRoguePlayableHeight() - 1); + } + + SetFeedbackMessage( + _T("最后一搏"), + _T("底部 3 行被清除,战局得以延续。"), + 14); + return true; + } + + gameOverFlag = true; + return false; +} + +/** + * @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。 + */ +static void SpawnNextFallingPiece() +{ + // 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。 + type = ConsumeNextType(); + nType = nextTypes[0]; + state = 0; + holdUsedThisTurn = false; + RollCurrentPieceSpecialFlags(true); + point = GetSpawnPoint(type); + target = point; + ComputeTarget(); +} + /** * @brief 将当前活动方块固定到工作区,并生成下一个活动方块。 * @@ -400,81 +523,13 @@ void Fixing() int explosiveCellCount = 0; pendingChainBombFollowup = false; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < 4; j++) - { - if (bricks[type][state][i][j] != 0) - { - int fixY = point.y + i; - int fixX = point.x + j; - - // 只要当前方块任意非空单元仍超出顶部,就标记为结束 - if (fixY < 0) - { - overflowTop = true; - } - - // 将当前方块在可视区域内的部分写入工作区 - if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth) - { - workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; - if (fixedCellCount < 4) - { - fixedCells[fixedCellCount].x = fixX; - fixedCells[fixedCellCount].y = fixY; - fixedCellCount++; - } - if (currentPieceIsExplosive && explosiveCellCount < 4) - { - explosiveCells[explosiveCellCount].x = fixX; - explosiveCells[explosiveCellCount].y = fixY; - explosiveCellCount++; - } - } - } - } - } + CollectAndWriteFixedCells(overflowTop, fixedCells, fixedCellCount, explosiveCells, explosiveCellCount); ApplyRainbowLandingEffect(overflowTop, fixedCells, fixedCellCount); - if (overflowTop) + if (!ResolveFixingOverflow(overflowTop)) { - if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) - { - rogueStats.lastChanceCount--; - rogueStats.screenBombCount--; - - int clearedByTerminal = TriggerScreenBomb(); - rogueStats.feverTicks = 10; - currentFallInterval = GetRogueFallInterval(); - - TCHAR terminalDetail[128]; - _stprintf_s( - terminalDetail, - _T("终末清场启动,清除 %d 格,并进入 10 秒狂热。"), - clearedByTerminal); - SetFeedbackMessage(_T("终末清场"), terminalDetail, 14); - } - else if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0) - { - rogueStats.lastChanceCount--; - - for (int i = 0; i < 3; i++) - { - DeleteOneLine(GetRoguePlayableHeight() - 1); - } - - SetFeedbackMessage( - _T("最后一搏"), - _T("底部 3 行被清除,战局得以延续。"), - 14); - } - else - { - gameOverFlag = true; - return; - } + return; } ApplySpecialLandingEffects(fixedCells, fixedCellCount, explosiveCells, explosiveCellCount); @@ -484,15 +539,7 @@ void Fixing() currentFallInterval = GetRogueFallInterval(); } - // 生成下一个活动方块 - type = ConsumeNextType(); - nType = nextTypes[0]; - state = 0; - holdUsedThisTurn = false; - RollCurrentPieceSpecialFlags(true); - point = GetSpawnPoint(type); - target = point; - ComputeTarget(); + SpawnNextFallingPiece(); } /** @@ -521,20 +568,15 @@ void DeleteOneLine(int number) } /** - * @brief 检查并删除所有已满的行,同时更新当前得分。 - * - * 该函数会从底部向上遍历工作区,判断每一行是否被完全填满。 - * 如果某一行全部非 0,则调用 DeleteOneLine 删除该行, - * 并将该行上方的内容整体下移。为了避免连续满行被漏检, - * 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。 - * - * @return 本次实际消除的行数。 + * @brief 从底向上扫描满行并删除,记录本次消除的原始行号。 + * @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。 + * @param clearedRowCount 返回记录的行号数量。 + * @return 本次标准消行数量。 */ -int DeleteLines() +static int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount) { int clearedLines = 0; - int clearedRows[8] = {}; - int clearedRowCount = 0; + clearedRowCount = 0; int playableHeight = GetRoguePlayableHeight(); for (int i = playableHeight - 1; i >= 0; i--) @@ -564,8 +606,17 @@ int DeleteLines() } } - // 消行数量先进入玩法结算,再根据是否正在升级决定动画立即播放还是暂存。 - ApplyLineClearResult(clearedLines); + return clearedLines; +} + +/** + * @brief 根据当前界面状态立即播放或暂存消行动画。 + * @param clearedRows 已消除行号数组。 + * @param clearedRowCount 行号数量。 + * @param clearedLines 本次消行数量。 + */ +static void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines) +{ if (currentScreen == SCREEN_UPGRADE) { QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines); @@ -574,55 +625,84 @@ int DeleteLines() { TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines); } +} - // 连环炸弹的追加爆破只在爆破方块导致后续消行时触发一次。 - if (pendingChainBombFollowup && clearedLines > 0) +/** + * @brief 处理连环炸弹因消行触发的一次追加 3x3 爆破。 + * @param clearedLines 本次标准消行数量。 + */ +static void ResolveChainBombFollowup(int clearedLines) +{ + if (!pendingChainBombFollowup || clearedLines <= 0) { pendingChainBombFollowup = false; + return; + } - int followupCleared = 0; - int centerY = pendingChainBombCenter.y; - int centerX = pendingChainBombCenter.x; - Point followupCells[9] = {}; + pendingChainBombFollowup = false; - for (int y = centerY - 1; y <= centerY + 1; y++) + int followupCleared = 0; + int centerY = pendingChainBombCenter.y; + int centerX = pendingChainBombCenter.x; + Point followupCells[9] = {}; + + for (int y = centerY - 1; y <= centerY + 1; y++) + { + for (int x = centerX - 1; x <= centerX + 1; x++) { - for (int x = centerX - 1; x <= centerX + 1; x++) + if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) { - if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) + if (followupCleared < 9) { - if (followupCleared < 9) - { - followupCells[followupCleared].x = x; - followupCells[followupCleared].y = y; - } - workRegion[y][x] = 0; - followupCleared++; + followupCells[followupCleared].x = x; + followupCells[followupCleared].y = y; } + workRegion[y][x] = 0; + followupCleared++; } } - - if (currentMode == MODE_ROGUE && followupCleared > 0) - { - TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true); - int followupScore = 0; - int followupExp = 0; - AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false); - - TCHAR followupDetail[128]; - _stprintf_s( - followupDetail, - _T("追加爆炸清除 %d 格 +%d 分 +%d EXP"), - followupCleared, - followupScore, - followupExp); - SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12); - } } - else + + if (currentMode == MODE_ROGUE && followupCleared > 0) { - pendingChainBombFollowup = false; + TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true); + int followupScore = 0; + int followupExp = 0; + AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false); + + TCHAR followupDetail[128]; + _stprintf_s( + followupDetail, + _T("追加爆炸清除 %d 格 +%d 分 +%d EXP"), + followupCleared, + followupScore, + followupExp); + SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12); } +} + +/** + * @brief 检查并删除所有已满的行,同时更新当前得分。 + * + * 该函数会从底部向上遍历工作区,判断每一行是否被完全填满。 + * 如果某一行全部非 0,则调用 DeleteOneLine 删除该行, + * 并将该行上方的内容整体下移。为了避免连续满行被漏检, + * 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。 + * + * @return 本次实际消除的行数。 + */ +int DeleteLines() +{ + int clearedRows[8] = {}; + int clearedRowCount = 0; + int clearedLines = ScanAndDeleteFullLines(clearedRows, clearedRowCount); + + // 消行数量先进入玩法结算,再根据是否正在升级决定动画立即播放还是暂存。 + ApplyLineClearResult(clearedLines); + DispatchLineClearEffect(clearedRows, clearedRowCount, clearedLines); + + // 连环炸弹的追加爆破只在爆破方块导致后续消行时触发一次。 + ResolveChainBombFollowup(clearedLines); return clearedLines; } diff --git a/src/source/app/TetrisInput.cpp b/src/source/app/TetrisInput.cpp index 3aab344..44ab2d0 100644 --- a/src/source/app/TetrisInput.cpp +++ b/src/source/app/TetrisInput.cpp @@ -417,6 +417,88 @@ static bool HandleMenuKey(HWND hWnd, WPARAM key) return true; } +/** + * @brief 在帮助首页选项之间循环移动。 + * @param direction 负数向前,正数向后。 + */ +static void MoveHelpHomeSelection(int direction) +{ + helpState.selectedIndex += direction; + if (helpState.selectedIndex < 0) + { + helpState.selectedIndex = helpState.optionCount - 1; + } + if (helpState.selectedIndex >= helpState.optionCount) + { + helpState.selectedIndex = 0; + } +} + +/** + * @brief 在技能演示列表中移动选中项,并同步滚动偏移。 + * @param direction 负数向上,正数向下。 + */ +static void MoveSkillDemoSelection(int direction) +{ + helpState.selectedIndex += direction; + if (helpState.selectedIndex < 0) + { + helpState.selectedIndex = GetRogueSkillDemoCount() - 1; + } + if (helpState.selectedIndex >= GetRogueSkillDemoCount()) + { + helpState.selectedIndex = 0; + helpScrollOffset = 0; + } + else if (direction < 0 && helpState.selectedIndex * 68 < helpScrollOffset) + { + helpScrollOffset = helpState.selectedIndex * 68; + } + else if (direction > 0 && helpState.selectedIndex * 68 > helpScrollOffset + 360) + { + helpScrollOffset = helpState.selectedIndex * 68 - 360; + } +} + +/** + * @brief 打开帮助首页当前选中的子页面。 + */ +static void ActivateHelpSelection() +{ + if (helpState.selectedIndex == 3) + { + helpState.currentPage = 5; + helpState.selectedIndex = 0; + helpScrollOffset = 0; + } + else + { + helpState.currentPage = helpState.selectedIndex + 1; + helpScrollOffset = 0; + } +} + +/** + * @brief 从帮助子页返回帮助首页或主菜单。 + */ +static void LeaveRulesPage() +{ + int previousPage = helpState.currentPage; + if (helpState.currentPage == 0) + { + ReturnToMainMenu(); + } + else + { + helpState.currentPage = 0; + if (previousPage == 4 || previousPage == 5) + { + helpState.selectedIndex = 3; + } + helpScrollOffset = 0; + } +} + /** * @brief 处理帮助和致谢页键盘导航。 * @param hWnd 当前窗口句柄。 @@ -438,11 +520,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) case 'A': if (helpState.currentPage == 0) { - helpState.selectedIndex--; - if (helpState.selectedIndex < 0) - { - helpState.selectedIndex = helpState.optionCount - 1; - } + MoveHelpHomeSelection(-1); InvalidateRect(hWnd, nullptr, FALSE); } else if (helpState.currentPage == 4) @@ -452,15 +530,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) } else if (helpState.currentPage == 5) { - helpState.selectedIndex--; - if (helpState.selectedIndex < 0) - { - helpState.selectedIndex = GetRogueSkillDemoCount() - 1; - } - if (helpState.selectedIndex * 68 < helpScrollOffset) - { - helpScrollOffset = helpState.selectedIndex * 68; - } + MoveSkillDemoSelection(-1); InvalidateRect(hWnd, nullptr, FALSE); } break; @@ -470,11 +540,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) case 'D': if (helpState.currentPage == 0) { - helpState.selectedIndex++; - if (helpState.selectedIndex >= helpState.optionCount) - { - helpState.selectedIndex = 0; - } + MoveHelpHomeSelection(1); InvalidateRect(hWnd, nullptr, FALSE); } else if (helpState.currentPage == 4) @@ -484,16 +550,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) } else if (helpState.currentPage == 5) { - helpState.selectedIndex++; - if (helpState.selectedIndex >= GetRogueSkillDemoCount()) - { - helpState.selectedIndex = 0; - helpScrollOffset = 0; - } - else if (helpState.selectedIndex * 68 > helpScrollOffset + 360) - { - helpScrollOffset = helpState.selectedIndex * 68 - 360; - } + MoveSkillDemoSelection(1); InvalidateRect(hWnd, nullptr, FALSE); } break; @@ -501,17 +558,7 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) case VK_SPACE: if (helpState.currentPage == 0) { - if (helpState.selectedIndex == 3) - { - helpState.currentPage = 5; - helpState.selectedIndex = 0; - helpScrollOffset = 0; - } - else - { - helpState.currentPage = helpState.selectedIndex + 1; - helpScrollOffset = 0; - } + ActivateHelpSelection(); InvalidateRect(hWnd, nullptr, FALSE); } else if (helpState.currentPage == 5) @@ -524,24 +571,9 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) case VK_ESCAPE: case VK_BACK: case 'M': - { - int previousPage = helpState.currentPage; - if (helpState.currentPage == 0) - { - ReturnToMainMenu(); - } - else - { - helpState.currentPage = 0; - if (previousPage == 4 || previousPage == 5) - { - helpState.selectedIndex = 3; - } - helpScrollOffset = 0; - } + LeaveRulesPage(); InvalidateRect(hWnd, nullptr, FALSE); break; - } default: break; } @@ -549,6 +581,92 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) return true; } +/** + * @brief 计算升级卡片网格列数。 + * @return 当前升级界面使用的列数,至少为 1。 + */ +static int GetUpgradeColumnCount() +{ + int upgradeColumnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3; + if (upgradeColumnCount < 1) + { + upgradeColumnCount = 1; + } + return upgradeColumnCount; +} + +/** + * @brief 在升级卡片网格中横向移动选中项。 + * @param direction 负数向左,正数向右。 + */ +static void MoveUpgradeSelectionHorizontal(int direction) +{ + if (upgradeUiState.optionCount <= 1) + { + return; + } + + int upgradeColumnCount = GetUpgradeColumnCount(); + int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount); + int rowEnd = rowStart + upgradeColumnCount - 1; + if (rowEnd >= upgradeUiState.optionCount) + { + rowEnd = upgradeUiState.optionCount - 1; + } + + if (direction < 0) + { + upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex > rowStart) ? upgradeUiState.selectedIndex - 1 : rowEnd; + } + else + { + upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex < rowEnd) ? upgradeUiState.selectedIndex + 1 : rowStart; + } +} + +/** + * @brief 在升级卡片网格中纵向移动选中项。 + * @param direction 负数向上,正数向下。 + */ +static void MoveUpgradeSelectionVertical(int direction) +{ + int upgradeColumnCount = GetUpgradeColumnCount(); + if (direction < 0 && upgradeUiState.selectedIndex >= upgradeColumnCount) + { + upgradeUiState.selectedIndex -= upgradeColumnCount; + } + else if (direction > 0 && upgradeUiState.selectedIndex + upgradeColumnCount < upgradeUiState.optionCount) + { + upgradeUiState.selectedIndex += upgradeColumnCount; + } +} + +/** + * @brief 切换多选升级中的当前卡片标记状态。 + */ +static void ToggleUpgradeMarkedSelection() +{ + if (upgradeUiState.picksRemaining <= 1 || upgradeUiState.optionCount <= 0) + { + return; + } + + bool currentlyMarked = upgradeUiState.marked[upgradeUiState.selectedIndex]; + if (currentlyMarked) + { + upgradeUiState.marked[upgradeUiState.selectedIndex] = false; + if (upgradeUiState.markedCount > 0) + { + upgradeUiState.markedCount--; + } + } + else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining) + { + upgradeUiState.marked[upgradeUiState.selectedIndex] = true; + upgradeUiState.markedCount++; + } +} + /** * @brief 处理升级选择界面键盘导航。 * @param hWnd 当前窗口句柄。 @@ -562,64 +680,26 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key) return false; } - int upgradeColumnCount = upgradeUiState.optionCount <= 3 ? upgradeUiState.optionCount : 3; - if (upgradeColumnCount < 1) - { - upgradeColumnCount = 1; - } - switch (key) { case VK_LEFT: case 'A': - if (upgradeUiState.optionCount > 1) - { - int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount); - if (upgradeUiState.selectedIndex > rowStart) - { - upgradeUiState.selectedIndex--; - } - else - { - int rowEnd = rowStart + upgradeColumnCount - 1; - if (rowEnd >= upgradeUiState.optionCount) - { - rowEnd = upgradeUiState.optionCount - 1; - } - upgradeUiState.selectedIndex = rowEnd; - } - } + MoveUpgradeSelectionHorizontal(-1); InvalidateRect(hWnd, nullptr, FALSE); break; case VK_RIGHT: case 'D': - if (upgradeUiState.optionCount > 1) - { - int rowStart = upgradeUiState.selectedIndex - (upgradeUiState.selectedIndex % upgradeColumnCount); - int rowEnd = rowStart + upgradeColumnCount - 1; - if (rowEnd >= upgradeUiState.optionCount) - { - rowEnd = upgradeUiState.optionCount - 1; - } - - upgradeUiState.selectedIndex = (upgradeUiState.selectedIndex < rowEnd) ? upgradeUiState.selectedIndex + 1 : rowStart; - } + MoveUpgradeSelectionHorizontal(1); InvalidateRect(hWnd, nullptr, FALSE); break; case VK_UP: case 'W': - if (upgradeUiState.selectedIndex >= upgradeColumnCount) - { - upgradeUiState.selectedIndex -= upgradeColumnCount; - } + MoveUpgradeSelectionVertical(-1); InvalidateRect(hWnd, nullptr, FALSE); break; case VK_DOWN: case 'S': - if (upgradeUiState.selectedIndex + upgradeColumnCount < upgradeUiState.optionCount) - { - upgradeUiState.selectedIndex += upgradeColumnCount; - } + MoveUpgradeSelectionVertical(1); InvalidateRect(hWnd, nullptr, FALSE); break; case VK_RETURN: @@ -630,20 +710,7 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key) case VK_SPACE: if (upgradeUiState.picksRemaining > 1 && upgradeUiState.optionCount > 0) { - bool currentlyMarked = upgradeUiState.marked[upgradeUiState.selectedIndex]; - if (currentlyMarked) - { - upgradeUiState.marked[upgradeUiState.selectedIndex] = false; - if (upgradeUiState.markedCount > 0) - { - upgradeUiState.markedCount--; - } - } - else if (upgradeUiState.markedCount < upgradeUiState.picksRemaining) - { - upgradeUiState.marked[upgradeUiState.selectedIndex] = true; - upgradeUiState.markedCount++; - } + ToggleUpgradeMarkedSelection(); InvalidateRect(hWnd, nullptr, FALSE); } else @@ -665,40 +732,54 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key) } /** - * @brief 处理游戏过程中的按键。 + * @brief 处理 Rogue 技能演示模式的专用按键。 * @param hWnd 当前窗口句柄。 * @param key 按键虚拟键码。 + * @return 已处理返回 true。 */ -static void HandlePlayingKey(HWND hWnd, WPARAM key) +static bool HandleDemoPlayingKey(HWND hWnd, WPARAM key) { - if (IsRogueSkillDemoMode()) + if (!IsRogueSkillDemoMode()) { - if (key == 'N') - { - AdvanceRogueSkillDemo(); - InvalidateRect(hWnd, nullptr, FALSE); - return; - } - if (key == 'R') - { - RestartCurrentRogueSkillDemo(); - ResetGameTimer(hWnd); - InvalidateRect(hWnd, nullptr, FALSE); - return; - } - if (key == VK_ESCAPE || key == VK_BACK || key == 'M') - { - OpenSkillDemoScreen(); - InvalidateRect(hWnd, nullptr, FALSE); - return; - } + return false; } + if (key == 'N') + { + AdvanceRogueSkillDemo(); + InvalidateRect(hWnd, nullptr, FALSE); + return true; + } + if (key == 'R') + { + RestartCurrentRogueSkillDemo(); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + return true; + } + if (key == VK_ESCAPE || key == VK_BACK || key == 'M') + { + OpenSkillDemoScreen(); + InvalidateRect(hWnd, nullptr, FALSE); + return true; + } + + return false; +} + +/** + * @brief 处理正常战局控制键,如菜单、重开、暂停、目标提示和复活。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + * @return 已处理返回 true。 + */ +static bool HandleBattleControlKey(HWND hWnd, WPARAM key) +{ if (!IsRogueSkillDemoMode() && key == 'M') { ReturnToMainMenu(); InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } if (!IsRogueSkillDemoMode() && key == 'R') @@ -706,21 +787,21 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) StartGameWithMode(currentMode); ResetGameTimer(hWnd); InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } if (!IsRogueSkillDemoMode() && key == 'P') { suspendFlag = !suspendFlag; InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } if (key == 'G') { targetFlag = !targetFlag; InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } if (gameOverFlag && reviveAvailable && key == 'V') @@ -735,23 +816,70 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) SetFeedbackMessage(_T("视频播放失败"), _T("无法打开复活视频,复活机会未消耗。"), 14); } InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } - if (gameOverFlag || suspendFlag) - { - return; - } + return false; +} +/** + * @brief 处理 Rogue 侧栏滚动和主动技能按键。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + * @return 已处理返回 true。 + */ +static bool HandleRogueSkillKey(HWND hWnd, WPARAM key) +{ if (currentMode == MODE_ROGUE && (key == 'J' || key == 'K')) { int direction = (key == 'J') ? 1 : -1; AdjustScrollOffset(upgradeListScrollOffset, direction * GetScrollStep(hWnd, 52)); InvalidateRect(hWnd, nullptr, FALSE); - return; + return true; } - // 正常游玩按键先改变方块或触发技能,再统一刷新预测落点和界面。 + switch (key) + { + case 'C': + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + HoldCurrentPiece(); + return true; + case 'Z': + UseBlackHole(); + return true; + case 'X': + UseScreenBomb(); + return true; + case 'V': + UseAirReshape(); + return true; + default: + return false; + } +} + +/** + * @brief 固定当前方块后执行消行和 Rogue 升级检查。 + */ +static void FixPieceAndResolveLines() +{ + Fixing(); + if (!gameOverFlag) + { + DeleteLines(); + CheckRogueLevelProgress(); + } +} + +/** + * @brief 处理移动、旋转、软降和硬降等方块操作键。 + * @param key 按键虚拟键码。 + * @return 已处理返回 true。 + */ +static bool HandlePieceMovementKey(WPARAM key) +{ switch (key) { case VK_LEFT: @@ -760,14 +888,14 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) { MoveLeft(); } - break; + return true; case VK_RIGHT: case 'D': if (CanMoveRight()) { MoveRight(); } - break; + return true; case VK_DOWN: case 'S': if (CanMoveDown()) @@ -776,44 +904,43 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) } else { - Fixing(); - if (!gameOverFlag) - { - DeleteLines(); - CheckRogueLevelProgress(); - } + FixPieceAndResolveLines(); } - break; + return true; case VK_UP: case 'W': Rotate(); - break; + return true; case VK_SPACE: DropDown(); - Fixing(); - if (!gameOverFlag) - { - DeleteLines(); - CheckRogueLevelProgress(); - } - break; - case 'C': - case VK_SHIFT: - case VK_LSHIFT: - case VK_RSHIFT: - HoldCurrentPiece(); - break; - case 'Z': - UseBlackHole(); - break; - case 'X': - UseScreenBomb(); - break; - case 'V': - UseAirReshape(); - break; + FixPieceAndResolveLines(); + return true; default: - break; + return false; + } +} + +/** + * @brief 处理游戏过程中的按键。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + */ +static void HandlePlayingKey(HWND hWnd, WPARAM key) +{ + if (HandleDemoPlayingKey(hWnd, key) || HandleBattleControlKey(hWnd, key)) + { + return; + } + + if (gameOverFlag || suspendFlag) + { + return; + } + + // 正常游玩按键先改变方块或触发技能,再统一刷新预测落点和界面。 + if (!HandlePieceMovementKey(key)) + { + HandleRogueSkillKey(hWnd, key); } if (!gameOverFlag) diff --git a/src/source/rogue/TetrisRogue.cpp b/src/source/rogue/TetrisRogue.cpp index 9b257b9..71f77be 100644 --- a/src/source/rogue/TetrisRogue.cpp +++ b/src/source/rogue/TetrisRogue.cpp @@ -1555,18 +1555,15 @@ static int TriggerUpgradeShockwave(int rowsToClear) } /** - * @brief 按权重和当前局势生成升级菜单中的候选强化。 - * - * 该函数只负责生成 UI 选项,不应用强化效果。候选池会先过滤前置、 - * 等级上限和互斥条件,再按动态权重不放回抽取,保证同一轮不会重复出现。 + * @brief 收集当前可进入升级池的强化下标和动态权重。 + * @param selectableIndexes 返回候选在强化池中的下标。 + * @param selectableWeights 返回候选动态权重。 + * @return 候选数量。 */ -static void FillUpgradeOptions() +static int CollectSelectableUpgrades(int selectableIndexes[], int selectableWeights[]) { - int selectableIndexes[kUpgradePoolSize] = { 0 }; - int selectableWeights[kUpgradePoolSize] = { 0 }; int selectableCount = 0; - // 第一段:筛出当前真正可选的强化,并记录它们的动态权重。 for (int i = 0; i < kUpgradePoolSize; i++) { if (IsUpgradeSelectable(kUpgradePool[i])) @@ -1577,7 +1574,16 @@ static void FillUpgradeOptions() } } - // 命运轮盘把候选扩到 6 个;双重抉择和命运轮盘都会允许本轮选 2 个。 + return selectableCount; +} + +/** + * @brief 初始化本轮升级界面的数量、选中项和多选状态。 + * @param selectableCount 当前可选候选数量。 + * @return 本轮实际展示的选项数量。 + */ +static int PrepareUpgradeOptionState(int selectableCount) +{ int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 6 : 3; int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit; upgradeUiState.optionCount = optionCount; @@ -1589,71 +1595,114 @@ static void FillUpgradeOptions() upgradeUiState.marked[i] = false; } + return optionCount; +} + +/** + * @brief 按权重从候选数组中抽取一个槽位。 + * @param selectableWeights 候选权重数组。 + * @param selectableCount 候选数量。 + * @return 被抽中的候选槽位。 + */ +static int PickUpgradeSlotByWeight(const int selectableWeights[], int selectableCount) +{ + int totalWeight = 0; + for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) + { + totalWeight += selectableWeights[weightIndex]; + } + + int pickSlot = 0; + if (totalWeight > 0) + { + int roll = rand() % totalWeight; + int accumulatedWeight = 0; + + for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) + { + accumulatedWeight += selectableWeights[weightIndex]; + if (roll < accumulatedWeight) + { + pickSlot = weightIndex; + break; + } + } + } + + return pickSlot; +} + +/** + * @brief 将抽中的强化池条目写入升级 UI 选项。 + * @param optionIndex UI 选项下标。 + * @param pickedEntry 被抽中的强化池条目。 + */ +static void FillUpgradeOptionFromEntry(int optionIndex, const UpgradeEntry& pickedEntry) +{ + upgradeUiState.options[optionIndex].id = pickedEntry.id; + upgradeUiState.options[optionIndex].currentLevel = GetUpgradeCurrentLevel(pickedEntry.id); + upgradeUiState.options[optionIndex].targetPieceType = -1; + upgradeUiState.options[optionIndex].rarity = GetUpgradeBaseRarity(pickedEntry.id); + upgradeUiState.options[optionIndex].cursed = false; + upgradeUiState.options[optionIndex].name = pickedEntry.name; + upgradeUiState.options[optionIndex].category = pickedEntry.category; + upgradeUiState.options[optionIndex].description = pickedEntry.description; + + if (pickedEntry.id == UPGRADE_PIECE_TUNING) + { + int targetPieceType = 0; + upgradeUiState.options[optionIndex].targetPieceType = targetPieceType; + upgradeUiState.options[optionIndex].currentLevel = rogueStats.pieceTuningLevels[0]; + upgradeUiState.options[optionIndex].name = _T("方块改造"); + + static TCHAR tuningDescriptions[6][64]; + _stprintf_s( + tuningDescriptions[optionIndex], + _T("%s 块的生成概率提高。"), + GetPieceShortName(0)); + upgradeUiState.options[optionIndex].description = tuningDescriptions[optionIndex]; + } +} + +/** + * @brief 命运轮盘生效时,为本轮候选随机附加一个诅咒标记。 + * @param optionCount 本轮展示选项数量。 + */ +static void MarkDestinyCursedOption(int optionCount) +{ + if (rogueStats.destinyWheelLevel > 0 && optionCount > 0) + { + int cursedIndex = rand() % optionCount; + upgradeUiState.options[cursedIndex].cursed = true; + } +} + +/** + * @brief 按权重和当前局势生成升级菜单中的候选强化。 + * + * 该函数只负责生成 UI 选项,不应用强化效果。候选池会先过滤前置、 + * 等级上限和互斥条件,再按动态权重不放回抽取,保证同一轮不会重复出现。 + */ +static void FillUpgradeOptions() +{ + int selectableIndexes[kUpgradePoolSize] = { 0 }; + int selectableWeights[kUpgradePoolSize] = { 0 }; + int selectableCount = CollectSelectableUpgrades(selectableIndexes, selectableWeights); + int optionCount = PrepareUpgradeOptionState(selectableCount); + // 第二段:按权重不放回抽取候选,抽中后用末尾元素覆盖当前槽位。 for (int i = 0; i < optionCount; i++) { - int totalWeight = 0; - for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) - { - totalWeight += selectableWeights[weightIndex]; - } - - int pickSlot = 0; - if (totalWeight > 0) - { - int roll = rand() % totalWeight; - int accumulatedWeight = 0; - - for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) - { - accumulatedWeight += selectableWeights[weightIndex]; - if (roll < accumulatedWeight) - { - pickSlot = weightIndex; - break; - } - } - } - + int pickSlot = PickUpgradeSlotByWeight(selectableWeights, selectableCount); int pickedIndex = selectableIndexes[pickSlot]; - const UpgradeEntry& pickedEntry = kUpgradePool[pickedIndex]; - - upgradeUiState.options[i].id = pickedEntry.id; - upgradeUiState.options[i].currentLevel = GetUpgradeCurrentLevel(pickedEntry.id); - upgradeUiState.options[i].targetPieceType = -1; - upgradeUiState.options[i].rarity = GetUpgradeBaseRarity(pickedEntry.id); - upgradeUiState.options[i].cursed = false; - upgradeUiState.options[i].name = pickedEntry.name; - upgradeUiState.options[i].category = pickedEntry.category; - upgradeUiState.options[i].description = pickedEntry.description; - - // 方块改造目前固定展示 I 块概率提升,说明文字需要运行时拼接。 - if (pickedEntry.id == UPGRADE_PIECE_TUNING) - { - int targetPieceType = 0; - upgradeUiState.options[i].targetPieceType = targetPieceType; - upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[0]; - upgradeUiState.options[i].name = _T("方块改造"); - - static TCHAR tuningDescriptions[6][64]; - _stprintf_s( - tuningDescriptions[i], - _T("%s 块的生成概率提高。"), - GetPieceShortName(0)); - upgradeUiState.options[i].description = tuningDescriptions[i]; - } + FillUpgradeOptionFromEntry(i, kUpgradePool[pickedIndex]); selectableIndexes[pickSlot] = selectableIndexes[selectableCount - 1]; selectableWeights[pickSlot] = selectableWeights[selectableCount - 1]; selectableCount--; } - // 命运轮盘在候选中随机附加一个诅咒,确认时才提高下一次升级需求。 - if (rogueStats.destinyWheelLevel > 0 && optionCount > 0) - { - int cursedIndex = rand() % optionCount; - upgradeUiState.options[cursedIndex].cursed = true; - } + MarkDestinyCursedOption(optionCount); } /** @@ -1708,6 +1757,241 @@ int GetRogueFallInterval() return baseInterval; } +/** + * @brief 应用成长、保命和基础操作类强化。 + * @param upgradeId 强化编号。 + * @param applyCount 本次应用次数。 + * @return 当前强化已处理返回 true。 + */ +static bool ApplyGrowthOrOperationUpgrade(int upgradeId, int applyCount) +{ + switch (upgradeId) + { + case UPGRADE_SCORE_MULTIPLIER: + rogueStats.scoreMultiplierPercent += 20 * applyCount; + rogueStats.scoreUpgradeLevel += applyCount; + return true; + case UPGRADE_COMBO_BONUS: + rogueStats.comboBonusStacks += applyCount; + return true; + case UPGRADE_EXP_MULTIPLIER: + rogueStats.expMultiplierPercent += 25 * applyCount; + rogueStats.expUpgradeLevel += applyCount; + return true; + case UPGRADE_SLOW_FALL: + rogueStats.slowFallStacks += applyCount; + if (rogueStats.slowFallStacks > 4) + { + rogueStats.slowFallStacks = 4; + } + currentFallInterval = GetRogueFallInterval(); + return true; + case UPGRADE_PREVIEW_PLUS_ONE: + if (rogueStats.previewCount < 2) + { + rogueStats.previewCount = 2; + } + rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; + return true; + case UPGRADE_LAST_CHANCE: + rogueStats.lastChanceCount += applyCount; + rogueStats.lastChanceUpgradeLevel += applyCount; + return true; + case UPGRADE_HOLD_UNLOCK: + rogueStats.holdUnlocked = 1; + holdUsedThisTurn = false; + return true; + case UPGRADE_PERFECT_ROTATE: + rogueStats.perfectRotateLevel = 1; + return true; + case UPGRADE_TIME_DILATION: + rogueStats.timeDilationLevel = 1; + currentFallInterval = GetRogueFallInterval(); + return true; + case UPGRADE_CONTROL_MASTER: + rogueStats.controlMasterLevel = 1; + if (rogueStats.previewCount < 3) + { + rogueStats.previewCount++; + } + rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; + return true; + case UPGRADE_DOUBLE_GROWTH: + rogueStats.doubleGrowthLevel = 1; + rogueStats.scoreMultiplierPercent += 15; + rogueStats.expMultiplierPercent += 15; + return true; + default: + return false; + } +} + +/** + * @brief 应用特殊方块和落地派生类强化。 + * @param upgradeId 强化编号。 + * @param applyCount 本次应用次数。 + * @return 当前强化已处理返回 true。 + */ +static bool ApplySpecialPieceUpgrade(int upgradeId, int applyCount) +{ + switch (upgradeId) + { + case UPGRADE_PRESSURE_RELIEF: + rogueStats.pressureReliefLevel += applyCount; + for (int i = 0; i < applyCount; i++) + { + int topOccupiedRow = GetTopOccupiedRow(); + if (topOccupiedRow >= 0) + { + DeleteOneLine(topOccupiedRow); + } + } + return true; + case UPGRADE_SWEEPER: + rogueStats.sweeperLevel += applyCount; + if (rogueStats.sweeperLevel > 4) + { + rogueStats.sweeperLevel = 4; + } + return true; + case UPGRADE_EXPLOSIVE_PIECE: + rogueStats.explosiveLevel = 1; + return true; + case UPGRADE_CHAIN_BLAST: + rogueStats.chainBlastLevel = 1; + return true; + case UPGRADE_CHAIN_BOMB: + rogueStats.chainBombLevel = 1; + return true; + case UPGRADE_LASER_PIECE: + rogueStats.laserLevel = 1; + return true; + case UPGRADE_THUNDER_TETRIS: + rogueStats.thunderTetrisLevel = 1; + return true; + case UPGRADE_THUNDER_LASER: + rogueStats.thunderLaserLevel = 1; + return true; + case UPGRADE_CROSS_PIECE: + rogueStats.crossPieceLevel = 1; + return true; + case UPGRADE_RAINBOW_PIECE: + rogueStats.rainbowPieceLevel = 1; + return true; + case UPGRADE_STABLE_STRUCTURE: + rogueStats.stableStructureLevel += applyCount; + return true; + case UPGRADE_PIECE_TUNING: + rogueStats.pieceTuningLevels[0] += applyCount; + return true; + default: + return false; + } +} + +/** + * @brief 应用主动技能、爆发和资源次数类强化。 + * @param upgradeId 强化编号。 + * @param applyCount 本次应用次数。 + * @return 当前强化已处理返回 true。 + */ +static bool ApplyActiveSkillUpgrade(int upgradeId, int applyCount) +{ + switch (upgradeId) + { + case UPGRADE_FEVER_MODE: + rogueStats.feverLevel = 1; + return true; + case UPGRADE_RAGE_STACK: + rogueStats.rageStackLevel = 1; + return true; + case UPGRADE_INFINITE_FEVER: + rogueStats.infiniteFeverLevel = 1; + return true; + case UPGRADE_SCREEN_BOMB: + rogueStats.screenBombLevel += applyCount; + rogueStats.screenBombCount += applyCount; + return true; + case UPGRADE_TERMINAL_CLEAR: + rogueStats.terminalClearLevel = 1; + return true; + case UPGRADE_BLOCK_STORM: + rogueStats.blockStormLevel = 1; + rogueStats.blockStormPiecesRemaining = 5; + nextTypes[0] = 0; + nextTypes[1] = 0; + nextTypes[2] = 0; + return true; + case UPGRADE_BLACK_HOLE: + rogueStats.blackHoleLevel = 1; + rogueStats.blackHoleCharges += 2 * applyCount; + return true; + case UPGRADE_AIR_RESHAPE: + rogueStats.reshapeLevel += applyCount; + rogueStats.reshapeCharges += 2 * applyCount; + return true; + case UPGRADE_VOID_CORE: + rogueStats.voidCoreLevel = 1; + return true; + default: + return false; + } +} + +/** + * @brief 应用风险、升级规则和进化类强化。 + * @param upgradeId 强化编号。 + * @param applyCount 本次应用次数。 + * @return 当前强化已处理返回 true。 + */ +static bool ApplyRiskOrEvolutionUpgrade(int upgradeId, int applyCount) +{ + switch (upgradeId) + { + case UPGRADE_DUAL_CHOICE: + rogueStats.dualChoiceLevel = 1; + rogueStats.requiredExp = rogueStats.requiredExp * 130 / 100; + if (rogueStats.requiredExp < 10) + { + rogueStats.requiredExp = 10; + } + return true; + case UPGRADE_DESTINY_WHEEL: + rogueStats.destinyWheelLevel = 1; + return true; + case UPGRADE_HIGH_PRESSURE: + rogueStats.highPressureLevel = 1; + rogueStats.scoreMultiplierPercent += 50; + rogueStats.expMultiplierPercent += 50; + currentFallInterval = GetRogueFallInterval(); + return true; + case UPGRADE_TETRIS_GAMBLE: + rogueStats.tetrisGambleLevel = 1; + return true; + case UPGRADE_EXTREME_PLAYER: + rogueStats.extremePlayerLevel = 1; + rogueStats.extremeDangerTicks = 30; + rogueStats.extremeDangerLevel = 0; + currentFallInterval = GetRogueFallInterval(); + return true; + case UPGRADE_UPGRADE_SHOCKWAVE: + rogueStats.upgradeShockwaveLevel = 1; + return true; + case UPGRADE_EVOLUTION_IMPACT: + rogueStats.evolutionImpactLevel = 1; + return true; + case UPGRADE_GAMBLER: + rogueStats.gamblerLevel += applyCount; + if (rogueStats.gamblerLevel > 4) + { + rogueStats.gamblerLevel = 4; + } + return true; + default: + return false; + } +} + /** * @brief 根据强化编号把对应效果写入 Rogue 属性。 * @@ -1727,191 +2011,10 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) return; } - // 基础成长类强化直接叠加倍率或层数。 - switch (upgradeId) - { - case UPGRADE_SCORE_MULTIPLIER: - rogueStats.scoreMultiplierPercent += 20 * applyCount; - rogueStats.scoreUpgradeLevel += applyCount; - break; - case UPGRADE_COMBO_BONUS: - rogueStats.comboBonusStacks += applyCount; - break; - case UPGRADE_EXP_MULTIPLIER: - rogueStats.expMultiplierPercent += 25 * applyCount; - rogueStats.expUpgradeLevel += applyCount; - break; - case UPGRADE_SLOW_FALL: - rogueStats.slowFallStacks += applyCount; - if (rogueStats.slowFallStacks > 4) - { - rogueStats.slowFallStacks = 4; - } - currentFallInterval = GetRogueFallInterval(); - break; - case UPGRADE_PREVIEW_PLUS_ONE: - if (rogueStats.previewCount < 2) - { - rogueStats.previewCount = 2; - } - rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; - break; - case UPGRADE_LAST_CHANCE: - rogueStats.lastChanceCount += applyCount; - rogueStats.lastChanceUpgradeLevel += applyCount; - break; - case UPGRADE_HOLD_UNLOCK: - rogueStats.holdUnlocked = 1; - holdUsedThisTurn = false; - break; - case UPGRADE_PRESSURE_RELIEF: - { - // 卸压清场立即从最高占用行开始删除,直接改善当前局面。 - rogueStats.pressureReliefLevel += applyCount; - for (int i = 0; i < applyCount; i++) - { - int topOccupiedRow = GetTopOccupiedRow(); - if (topOccupiedRow >= 0) - { - DeleteOneLine(topOccupiedRow); - } - } - break; - } - case UPGRADE_SWEEPER: - rogueStats.sweeperLevel += applyCount; - if (rogueStats.sweeperLevel > 4) - { - rogueStats.sweeperLevel = 4; - } - break; - case UPGRADE_EXPLOSIVE_PIECE: - rogueStats.explosiveLevel = 1; - break; - case UPGRADE_CHAIN_BLAST: - rogueStats.chainBlastLevel = 1; - break; - case UPGRADE_CHAIN_BOMB: - rogueStats.chainBombLevel = 1; - break; - case UPGRADE_LASER_PIECE: - rogueStats.laserLevel = 1; - break; - case UPGRADE_THUNDER_TETRIS: - rogueStats.thunderTetrisLevel = 1; - break; - case UPGRADE_THUNDER_LASER: - rogueStats.thunderLaserLevel = 1; - break; - case UPGRADE_FEVER_MODE: - rogueStats.feverLevel = 1; - break; - case UPGRADE_RAGE_STACK: - rogueStats.rageStackLevel = 1; - break; - case UPGRADE_INFINITE_FEVER: - rogueStats.infiniteFeverLevel = 1; - break; - case UPGRADE_SCREEN_BOMB: - rogueStats.screenBombLevel += applyCount; - rogueStats.screenBombCount += applyCount; - break; - case UPGRADE_TERMINAL_CLEAR: - rogueStats.terminalClearLevel = 1; - break; - case UPGRADE_DUAL_CHOICE: - rogueStats.dualChoiceLevel = 1; - rogueStats.requiredExp = rogueStats.requiredExp * 130 / 100; - if (rogueStats.requiredExp < 10) - { - rogueStats.requiredExp = 10; - } - break; - case UPGRADE_DESTINY_WHEEL: - rogueStats.destinyWheelLevel = 1; - break; - case UPGRADE_PERFECT_ROTATE: - rogueStats.perfectRotateLevel = 1; - break; - case UPGRADE_TIME_DILATION: - rogueStats.timeDilationLevel = 1; - currentFallInterval = GetRogueFallInterval(); - break; - case UPGRADE_HIGH_PRESSURE: - rogueStats.highPressureLevel = 1; - rogueStats.scoreMultiplierPercent += 50; - rogueStats.expMultiplierPercent += 50; - currentFallInterval = GetRogueFallInterval(); - break; - case UPGRADE_TETRIS_GAMBLE: - rogueStats.tetrisGambleLevel = 1; - break; - case UPGRADE_EXTREME_PLAYER: - rogueStats.extremePlayerLevel = 1; - rogueStats.extremeDangerTicks = 30; - rogueStats.extremeDangerLevel = 0; - currentFallInterval = GetRogueFallInterval(); - break; - case UPGRADE_UPGRADE_SHOCKWAVE: - rogueStats.upgradeShockwaveLevel = 1; - break; - case UPGRADE_EVOLUTION_IMPACT: - rogueStats.evolutionImpactLevel = 1; - break; - case UPGRADE_CONTROL_MASTER: - rogueStats.controlMasterLevel = 1; - if (rogueStats.previewCount < 3) - { - rogueStats.previewCount++; - } - rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; - break; - case UPGRADE_BLOCK_STORM: - // 方块风暴会同时改写预览队列,确保玩家马上看到连续 I 块。 - rogueStats.blockStormLevel = 1; - rogueStats.blockStormPiecesRemaining = 5; - nextTypes[0] = 0; - nextTypes[1] = 0; - nextTypes[2] = 0; - break; - case UPGRADE_CROSS_PIECE: - rogueStats.crossPieceLevel = 1; - break; - case UPGRADE_BLACK_HOLE: - rogueStats.blackHoleLevel = 1; - rogueStats.blackHoleCharges += 2 * applyCount; - break; - case UPGRADE_AIR_RESHAPE: - rogueStats.reshapeLevel += applyCount; - rogueStats.reshapeCharges += 2 * applyCount; - break; - case UPGRADE_RAINBOW_PIECE: - rogueStats.rainbowPieceLevel = 1; - break; - case UPGRADE_VOID_CORE: - rogueStats.voidCoreLevel = 1; - break; - case UPGRADE_STABLE_STRUCTURE: - rogueStats.stableStructureLevel += applyCount; - break; - case UPGRADE_DOUBLE_GROWTH: - rogueStats.doubleGrowthLevel = 1; - rogueStats.scoreMultiplierPercent += 15; - rogueStats.expMultiplierPercent += 15; - break; - case UPGRADE_PIECE_TUNING: - rogueStats.pieceTuningLevels[0] += applyCount; - break; - case UPGRADE_GAMBLER: - rogueStats.gamblerLevel += applyCount; - if (rogueStats.gamblerLevel > 4) - { - rogueStats.gamblerLevel = 4; - } - break; - default: - break; - } + ApplyGrowthOrOperationUpgrade(upgradeId, applyCount) || + ApplySpecialPieceUpgrade(upgradeId, applyCount) || + ApplyActiveSkillUpgrade(upgradeId, applyCount) || + ApplyRiskOrEvolutionUpgrade(upgradeId, applyCount); } /** @@ -2473,6 +2576,109 @@ void OpenUpgradeMenu() currentScreen = SCREEN_UPGRADE; } +/** + * @brief 结束一轮升级选择,如果还有待处理升级则刷新下一轮选项。 + * @return 仍停留在升级界面返回 true;已回到游戏返回 false。 + */ +static bool FinishUpgradeSelectionRound() +{ + if (upgradeUiState.pendingCount > 0) + { + upgradeUiState.pendingCount--; + } + + if (upgradeUiState.pendingCount > 0) + { + FillUpgradeOptions(); + currentScreen = SCREEN_UPGRADE; + return true; + } + + upgradeUiState.optionCount = 0; + upgradeUiState.picksRemaining = 0; + upgradeUiState.markedCount = 0; + + currentScreen = SCREEN_PLAYING; + ResolvePendingUpgradeShockwave(); + PlayPendingLineClearEffect(); + return false; +} + +/** + * @brief 构造单选强化的反馈正文。 + * @param selectedOption 被选择的强化选项。 + * @param gamblerSuffix 赌徒契约附加说明。 + * @param feedbackDetail 返回反馈正文。 + * @param feedbackDetailCapacity 反馈正文缓冲区长度。 + */ +static void BuildSingleUpgradeFeedback(const UpgradeOption& selectedOption, const TCHAR* gamblerSuffix, TCHAR feedbackDetail[], int feedbackDetailCapacity) +{ + if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0) + { + _stprintf_s( + feedbackDetail, + feedbackDetailCapacity, + _T("%s 块的生成概率提高%s"), + GetPieceShortName(0), + gamblerSuffix); + } + else + { + _stprintf_s(feedbackDetail, feedbackDetailCapacity, _T("%s%s"), selectedOption.description, gamblerSuffix); + } + + if (selectedOption.cursed) + { + _stprintf_s( + feedbackDetail + lstrlen(feedbackDetail), + feedbackDetailCapacity - lstrlen(feedbackDetail), + _T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。")); + } +} + +/** + * @brief 应用多选升级中一个已标记的选项并拼接反馈。 + * @param selectedOption 被应用的选项。 + * @param appliedSelections 已应用选项数量。 + * @param feedbackTitle 返回反馈标题。 + * @param feedbackDetail 返回反馈正文。 + */ +static void ApplyMarkedUpgradeOption(const UpgradeOption& selectedOption, int& appliedSelections, TCHAR feedbackTitle[], TCHAR feedbackDetail[]) +{ + TCHAR gamblerSuffix[64] = _T(""); + int applyCount = RollGamblerApplyCount(gamblerSuffix, 64, true); + + ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount); + upgradeUiState.totalChosenCount++; + + if (selectedOption.cursed) + { + ApplyDestinyCurse(); + } + + if (appliedSelections == 0) + { + _stprintf_s(feedbackTitle, 64, _T("获得强化:%s"), selectedOption.name); + } + else + { + _stprintf_s(feedbackTitle, 64, _T("获得强化 x%d"), appliedSelections + 1); + } + + if (lstrlen(feedbackDetail) > 0) + { + _stprintf_s(feedbackDetail + lstrlen(feedbackDetail), 128 - lstrlen(feedbackDetail), _T(";")); + } + _stprintf_s( + feedbackDetail + lstrlen(feedbackDetail), + 128 - lstrlen(feedbackDetail), + _T("%s%s%s"), + selectedOption.name, + gamblerSuffix, + selectedOption.cursed ? _T(" [诅咒]") : _T("")); + appliedSelections++; +} + /** * @brief 确认升级菜单中的选择并应用对应强化效果。 * @@ -2507,59 +2713,11 @@ void ConfirmUpgradeSelection() } UpgradeOption selectedOption = upgradeUiState.options[optionIndex]; - TCHAR gamblerSuffix[64] = _T(""); - int applyCount = RollGamblerApplyCount(gamblerSuffix, 64, true); - - ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount); - upgradeUiState.totalChosenCount++; - - if (selectedOption.cursed) - { - ApplyDestinyCurse(); - } - - if (appliedSelections == 0) - { - _stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name); - } - else - { - _stprintf_s(feedbackTitle, _T("获得强化 x%d"), appliedSelections + 1); - } - - if (lstrlen(feedbackDetail) > 0) - { - _stprintf_s(feedbackDetail + lstrlen(feedbackDetail), 128 - lstrlen(feedbackDetail), _T(";")); - } - _stprintf_s( - feedbackDetail + lstrlen(feedbackDetail), - 128 - lstrlen(feedbackDetail), - _T("%s%s%s"), - selectedOption.name, - gamblerSuffix, - selectedOption.cursed ? _T(" [诅咒]") : _T("")); - appliedSelections++; + ApplyMarkedUpgradeOption(selectedOption, appliedSelections, feedbackTitle, feedbackDetail); } SetFeedbackMessage(feedbackTitle, feedbackDetail, 12); - if (upgradeUiState.pendingCount > 0) - { - upgradeUiState.pendingCount--; - } - - if (upgradeUiState.pendingCount > 0) - { - FillUpgradeOptions(); - currentScreen = SCREEN_UPGRADE; - return; - } - - upgradeUiState.optionCount = 0; - upgradeUiState.picksRemaining = 0; - upgradeUiState.markedCount = 0; - currentScreen = SCREEN_PLAYING; - ResolvePendingUpgradeShockwave(); - PlayPendingLineClearEffect(); + FinishUpgradeSelectionRound(); return; } @@ -2573,26 +2731,11 @@ void ConfirmUpgradeSelection() TCHAR feedbackTitle[64]; TCHAR feedbackDetail[128]; _stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name); - if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0) - { - _stprintf_s( - feedbackDetail, - _T("%s 块的生成概率提高%s"), - GetPieceShortName(0), - gamblerSuffix); - } - else - { - _stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix); - } + BuildSingleUpgradeFeedback(selectedOption, gamblerSuffix, feedbackDetail, 128); if (selectedOption.cursed) { ApplyDestinyCurse(); - _stprintf_s( - feedbackDetail + lstrlen(feedbackDetail), - 128 - lstrlen(feedbackDetail), - _T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。")); } SetFeedbackMessage(feedbackTitle, feedbackDetail, 12); @@ -2622,26 +2765,7 @@ void ConfirmUpgradeSelection() return; } - // 连续升级时重新生成下一轮强化;全部完成后回到游戏并播放延迟效果。 - if (upgradeUiState.pendingCount > 0) - { - upgradeUiState.pendingCount--; - } - - if (upgradeUiState.pendingCount > 0) - { - FillUpgradeOptions(); - currentScreen = SCREEN_UPGRADE; - return; - } - - upgradeUiState.optionCount = 0; - upgradeUiState.picksRemaining = 0; - upgradeUiState.markedCount = 0; - - currentScreen = SCREEN_PLAYING; - ResolvePendingUpgradeShockwave(); - PlayPendingLineClearEffect(); + FinishUpgradeSelectionRound(); } /**