From da741d1e564188fbcffcc8fe076ee78e0136da6b Mon Sep 17 00:00:00 2001 From: qihuanye <2728290997@qq.com> Date: Tue, 28 Apr 2026 15:03:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=B8=AE=E5=8A=A9=E9=A1=B5?= =?UTF-8?q?=E8=BD=AE=E6=92=AD=E6=A1=86=E6=9E=B6=E6=9C=AA=E5=AE=8C=E5=96=84?= =?UTF-8?q?=EF=BC=88=E5=AE=9E=E5=88=99=E4=B8=80=E5=9D=A8=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/include/Tetris.h | 5 + src/source/Tetris.cpp | 58 +++- src/source/TetrisLogic.cpp | 1 + src/source/TetrisLogicInnovation.cpp | 8 +- src/source/TetrisRender.cpp | 10 +- src/source/TetrisRogue.cpp | 422 +++++++++++++++++++++++++++ 6 files changed, 494 insertions(+), 10 deletions(-) diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 8dd9f44..1655d77 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -221,6 +221,7 @@ extern bool suspendFlag; extern bool targetFlag; extern bool bgmEnabled; extern bool reviveAvailable; +extern bool rogueDemoMode; extern int workRegion[20][10]; extern Point point; extern Point target; @@ -268,6 +269,10 @@ void Restart(); void StartGameWithMode(int mode); void ReturnToMainMenu(); void ReviveAfterVideo(); +void StartRogueSkillDemo(); +bool IsRogueSkillDemoMode(); +bool TickRogueSkillDemo(); +void AdvanceRogueSkillDemo(); void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); void OpenRulesScreen(); void OpenCreditScreen(); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 493ddc8..c28a21d 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -789,6 +789,19 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) shouldRefresh = true; } + if (IsRogueSkillDemoMode()) + { + if (TickRogueSkillDemo()) + { + shouldRefresh = true; + } + if (shouldRefresh) + { + InvalidateRect(hWnd, nullptr, FALSE); + } + break; + } + if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0) { rogueStats.feverTicks--; @@ -990,8 +1003,16 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) if (IsPointInRect(GetHelpOptionRect(hWnd, i), mouseX, mouseY)) { helpState.selectedIndex = i; - helpState.currentPage = i + 1; - helpScrollOffset = 0; + if (i == 3) + { + StartRogueSkillDemo(); + ResetGameTimer(hWnd); + } + else + { + helpState.currentPage = i + 1; + helpScrollOffset = 0; + } InvalidateRect(hWnd, nullptr, FALSE); break; } @@ -1259,8 +1280,16 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case VK_SPACE: if (helpState.currentPage == 0) { - helpState.currentPage = helpState.selectedIndex + 1; - helpScrollOffset = 0; + if (helpState.selectedIndex == 3) + { + StartRogueSkillDemo(); + ResetGameTimer(hWnd); + } + else + { + helpState.currentPage = helpState.selectedIndex + 1; + helpScrollOffset = 0; + } InvalidateRect(hWnd, nullptr, FALSE); } break; @@ -1398,6 +1427,27 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) break; } + if (IsRogueSkillDemoMode()) + { + if (wParam == VK_RETURN || wParam == VK_SPACE) + { + AdvanceRogueSkillDemo(); + InvalidateRect(hWnd, nullptr, FALSE); + } + else if (wParam == 'R') + { + StartRogueSkillDemo(); + ResetGameTimer(hWnd); + InvalidateRect(hWnd, nullptr, FALSE); + } + else if (wParam == VK_ESCAPE || wParam == VK_BACK || wParam == 'M') + { + ReturnToMainMenu(); + InvalidateRect(hWnd, nullptr, FALSE); + } + break; + } + if (wParam == 'M') { ReturnToMainMenu(); diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index dbeb5bf..97b210e 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -10,6 +10,7 @@ bool gameOverFlag = false; bool suspendFlag = false; bool targetFlag = false; bool reviveAvailable = false; +bool rogueDemoMode = false; int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; diff --git a/src/source/TetrisLogicInnovation.cpp b/src/source/TetrisLogicInnovation.cpp index 852b3a6..bf26c0d 100644 --- a/src/source/TetrisLogicInnovation.cpp +++ b/src/source/TetrisLogicInnovation.cpp @@ -518,6 +518,7 @@ void ReviveAfterVideo() */ void StartGameWithMode(int mode) { + rogueDemoMode = false; currentMode = mode; currentScreen = SCREEN_PLAYING; upgradeListScrollOffset = 0; @@ -531,6 +532,7 @@ void StartGameWithMode(int mode) */ void ReturnToMainMenu() { + rogueDemoMode = false; currentScreen = SCREEN_MENU; suspendFlag = false; gameOverFlag = false; @@ -558,10 +560,11 @@ void ReturnToMainMenu() */ void OpenRulesScreen() { + rogueDemoMode = false; currentScreen = SCREEN_RULES; suspendFlag = false; helpState.selectedIndex = 0; - helpState.optionCount = 3; + helpState.optionCount = 4; helpState.currentPage = 0; helpScrollOffset = 0; creditPageIndex = 0; @@ -574,10 +577,11 @@ void OpenRulesScreen() */ void OpenCreditScreen() { + rogueDemoMode = false; currentScreen = SCREEN_RULES; suspendFlag = false; helpState.selectedIndex = 0; - helpState.optionCount = 3; + helpState.optionCount = 4; helpState.currentPage = 4; helpScrollOffset = 0; creditPageIndex = 0; diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index c92774c..f05eef0 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -658,17 +658,19 @@ void TDrawScreen(HDC hdc, HWND hWnd) if (helpState.currentPage == 0) { - const TCHAR* optionTitles[3] = + const TCHAR* optionTitles[4] = { _T("\u6e38\u620f\u4ecb\u7ecd"), _T("\u64cd\u4f5c\u8bf4\u660e"), - _T("\u5f3a\u5316\u56fe\u9274") + _T("\u5f3a\u5316\u56fe\u9274"), + _T("\u6280\u80fd\u6f14\u793a") }; - const TCHAR* optionDetails[3] = + const TCHAR* optionDetails[4] = { _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") + _T("\u67e5\u770b Rogue \u6a21\u5f0f\u5168\u90e8\u5f3a\u5316\u7684\u7b80\u8981\u6548\u679c\u3002"), + _T("\u8fdb\u5165\u81ea\u52a8\u8f6e\u64ad\u6f14\u793a\uff0c\u4f9d\u6b21\u5c55\u793a Rogue \u4e3b\u8981\u6280\u80fd\u548c\u68cb\u76d8\u6548\u679c\u3002") }; int optionHeight = SS(100); diff --git a/src/source/TetrisRogue.cpp b/src/source/TetrisRogue.cpp index 87ed413..fbdddac 100644 --- a/src/source/TetrisRogue.cpp +++ b/src/source/TetrisRogue.cpp @@ -109,6 +109,58 @@ static constexpr int kExplosiveBaseInterval = 6; static constexpr int kExplosiveMinInterval = 3; static int pendingUpgradeShockwaveRows = 0; static bool pendingEvolutionImpactShockwave = false; +static int rogueDemoStepIndex = 0; +static int rogueDemoTicks = 0; + +enum RogueDemoKind +{ + DEMO_HOLD = 0, + DEMO_BLACK_HOLE, + DEMO_SCREEN_BOMB, + DEMO_AIR_RESHAPE, + DEMO_EXPLOSIVE, + DEMO_CHAIN_BOMB, + DEMO_LASER, + DEMO_CROSS, + DEMO_RAINBOW, + DEMO_VOID_CORE, + DEMO_SWEEPER, + DEMO_LAST_CHANCE, + DEMO_TIME_DILATION, + DEMO_UPGRADE_SHOCKWAVE, + DEMO_BLOCK_STORM, + DEMO_STABLE_STRUCTURE +}; + +struct RogueDemoStep +{ + int kind; + const TCHAR* name; + const TCHAR* detail; +}; + +static const RogueDemoStep kRogueDemoSteps[] = +{ + { DEMO_HOLD, _T("备用仓"), _T("演示 C / Shift 暂存当前方块,并换出备用方块。") }, + { DEMO_BLACK_HOLE, _T("黑洞奇点"), _T("吞噬棋盘中数量最多的一种固定方块。") }, + { DEMO_SCREEN_BOMB, _T("清屏炸弹"), _T("主动引爆后清理可玩区域底部 5 行。") }, + { DEMO_AIR_RESHAPE, _T("空中换形"), _T("把正在下落的方块重塑为 I 块。") }, + { DEMO_EXPLOSIVE, _T("爆破核心"), _T("爆破方块落地后清除 3x3 区域。") }, + { DEMO_CHAIN_BOMB, _T("连环炸弹"), _T("爆破范围进化为 5x5。") }, + { DEMO_LASER, _T("棱镜激光"), _T("激光方块落地后清除整列。") }, + { DEMO_CROSS, _T("十字方块"), _T("十字方块同时清除所在行与所在列。") }, + { DEMO_RAINBOW, _T("彩虹方块"), _T("按中心行主色清除,并把覆盖行染成主色。") }, + { DEMO_VOID_CORE, _T("虚空核心"), _T("黑洞生效后追加召来彩虹方块。") }, + { DEMO_SWEEPER, _T("底线清道夫"), _T("消行充能后自动清扫底部。") }, + { DEMO_LAST_CHANCE, _T("最后一搏"), _T("濒死时自动清理底部 3 行。") }, + { DEMO_TIME_DILATION, _T("时间缓流"), _T("堆叠过高时临时降低下落速度。") }, + { DEMO_UPGRADE_SHOCKWAVE, _T("升级冲击波"), _T("升级后清除底部多行。") }, + { DEMO_BLOCK_STORM, _T("方块风暴"), _T("接下来多个方块固定变为 I 块。") }, + { DEMO_STABLE_STRUCTURE, _T("稳定结构"), _T("落地后填补局部空洞,让结构更平整。") } +}; + +static constexpr int kRogueDemoStepCount = sizeof(kRogueDemoSteps) / sizeof(kRogueDemoSteps[0]); +static constexpr int kRogueDemoStepTicks = 7; static int GetUpgradeCurrentLevel(int upgradeId); static bool IsUpgradePrerequisiteConsumed(int upgradeId); @@ -136,6 +188,12 @@ static void FillUpgradeOptions(); static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount); static void ApplyDestinyCurse(); static void ClearLockedRows(); +static void ApplyRogueSkillDemoStep(); +static void ResetRogueDemoBoard(); +static void ShowRogueDemoFloatingName(const TCHAR* name); +static void FillRogueDemoCell(int y, int x, int value); +static void FillRogueDemoRows(int firstRow, int lastRow, int baseValue); +static void SetRogueDemoCurrentPiece(int pieceType, int pieceState, int x, int y); /** * @brief 限制 Rogue 模式的下一方块预览数量。 @@ -2705,3 +2763,367 @@ void UseAirReshape() SetFeedbackMessage(_T("空中换形"), detail, 12); ComputeTarget(); } + +/** + * @brief 判断当前是否处于 Rogue 技能自动演示模式。 + */ +bool IsRogueSkillDemoMode() +{ + return rogueDemoMode; +} + +/** + * @brief 从帮助页进入 Rogue 技能演示,并播放第一段技能展示。 + */ +void StartRogueSkillDemo() +{ + currentMode = MODE_ROGUE; + currentScreen = SCREEN_PLAYING; + rogueDemoMode = true; + rogueDemoStepIndex = 0; + rogueDemoTicks = 0; + + Restart(); + rogueDemoMode = true; + reviveAvailable = false; + suspendFlag = false; + gameOverFlag = false; + targetFlag = true; + upgradeListScrollOffset = 0; + currentFallInterval = 500; + + ApplyRogueSkillDemoStep(); +} + +/** + * @brief 推进 Rogue 技能演示计时,到点后切换到下一项技能。 + */ +bool TickRogueSkillDemo() +{ + if (!rogueDemoMode) + { + return false; + } + + rogueDemoTicks++; + if (rogueDemoTicks >= kRogueDemoStepTicks) + { + AdvanceRogueSkillDemo(); + } + + return true; +} + +/** + * @brief 立刻切换到下一项 Rogue 技能演示。 + */ +void AdvanceRogueSkillDemo() +{ + if (!rogueDemoMode) + { + return; + } + + rogueDemoStepIndex++; + if (rogueDemoStepIndex >= kRogueDemoStepCount) + { + rogueDemoStepIndex = 0; + } + rogueDemoTicks = 0; + ApplyRogueSkillDemoStep(); +} + +/** + * @brief 清空棋盘和演示状态,为单个技能配置专用局面。 + */ +static void ResetRogueDemoBoard() +{ + for (int y = 0; y < nGameHeight; y++) + { + for (int x = 0; x < nGameWidth; x++) + { + workRegion[y][x] = 0; + } + } + + ResetPlayerStats(rogueStats, true); + ResetUpgradeUiState(); + ResetPendingRogueVisualEvents(); + ResetVisualEffects(); + pendingLineClearEffectTicks = 0; + pendingLineClearEffectRowCount = 0; + pendingLineClearEffectLineCount = 0; + clearEffectState.ticks = 0; + currentPieceIsExplosive = false; + currentPieceIsLaser = false; + currentPieceIsCross = false; + currentPieceIsRainbow = false; + holdType = -1; + holdUsedThisTurn = false; + tScore = 0; + currentScreen = SCREEN_PLAYING; + currentMode = MODE_ROGUE; + gameOverFlag = false; + suspendFlag = false; + reviveAvailable = false; + currentFallInterval = 500; + ResetNextQueue(); + SetRogueDemoCurrentPiece(1, 0, 3, 0); +} + +/** + * @brief 在棋盘中央显示当前演示技能名称。 + */ +static void ShowRogueDemoFloatingName(const TCHAR* name) +{ + floatingTextEffects[0].ticks = 56; + floatingTextEffects[0].totalTicks = 56; + floatingTextEffects[0].boardX = 500; + floatingTextEffects[0].boardY = 980; + floatingTextEffects[0].color = RGB(255, 248, 250); + lstrcpyn(floatingTextEffects[0].text, name, sizeof(floatingTextEffects[0].text) / sizeof(TCHAR)); +} + +/** + * @brief 安全写入演示棋盘中的一个固定方块格。 + */ +static void FillRogueDemoCell(int y, int x, int value) +{ + if (y < 0 || y >= nGameHeight || x < 0 || x >= nGameWidth) + { + return; + } + workRegion[y][x] = value; +} + +/** + * @brief 按交错颜色填充演示棋盘中的连续行。 + */ +static void FillRogueDemoRows(int firstRow, int lastRow, int baseValue) +{ + if (firstRow < 0) + { + firstRow = 0; + } + if (lastRow >= nGameHeight) + { + lastRow = nGameHeight - 1; + } + + for (int y = firstRow; y <= lastRow; y++) + { + for (int x = 0; x < nGameWidth; x++) + { + workRegion[y][x] = 1 + ((baseValue + y + x) % 7); + } + } +} + +/** + * @brief 设置演示模式下当前下落方块的类型、旋转和位置。 + */ +static void SetRogueDemoCurrentPiece(int pieceType, int pieceState, int x, int y) +{ + type = pieceType; + nType = 0; + state = pieceState; + point.x = x; + point.y = y; + target = point; + nextTypes[0] = 0; + nextTypes[1] = 0; + nextTypes[2] = 0; + ComputeTarget(); +} + +/** + * @brief 配置并触发当前 Rogue 技能演示步骤。 + */ +static void ApplyRogueSkillDemoStep() +{ + ResetRogueDemoBoard(); + + const RogueDemoStep& demoStep = kRogueDemoSteps[rogueDemoStepIndex]; + ShowRogueDemoFloatingName(demoStep.name); + SetFeedbackMessage(demoStep.name, demoStep.detail, 12); + + switch (demoStep.kind) + { + case DEMO_HOLD: + rogueStats.holdUnlocked = 1; + rogueStats.controlMasterLevel = 1; + holdType = 0; + SetRogueDemoCurrentPiece(2, 0, 3, 1); + HoldCurrentPiece(); + break; + + case DEMO_BLACK_HOLE: + rogueStats.blackHoleLevel = 1; + rogueStats.blackHoleCharges = 1; + FillRogueDemoRows(8, 19, 2); + for (int y = 8; y < nGameHeight; y++) + { + for (int x = 0; x < nGameWidth; x += 3) + { + workRegion[y][x] = 2; + } + } + UseBlackHole(); + break; + + case DEMO_SCREEN_BOMB: + rogueStats.screenBombLevel = 1; + rogueStats.screenBombCount = 1; + FillRogueDemoRows(12, 19, 0); + UseScreenBomb(); + break; + + case DEMO_AIR_RESHAPE: + rogueStats.reshapeLevel = 1; + rogueStats.reshapeCharges = 1; + FillRogueDemoRows(15, 19, 3); + SetRogueDemoCurrentPiece(1, 0, 3, 4); + UseAirReshape(); + break; + + case DEMO_EXPLOSIVE: + FillRogueDemoRows(11, 17, 1); + currentPieceIsExplosive = true; + SetRogueDemoCurrentPiece(5, 0, 3, 8); + ClearExplosiveAreaAt(14, 5); + ApplyBoardGravity(); + break; + + case DEMO_CHAIN_BOMB: + rogueStats.chainBombLevel = 1; + FillRogueDemoRows(10, 18, 4); + currentPieceIsExplosive = true; + SetRogueDemoCurrentPiece(5, 0, 3, 8); + ClearExplosiveAreaAt(14, 5); + ApplyBoardGravity(); + break; + + case DEMO_LASER: + for (int y = 4; y < nGameHeight; y++) + { + FillRogueDemoCell(y, 5, 4); + if (y >= 12) + { + FillRogueDemoCell(y, 3, 2); + FillRogueDemoCell(y, 7, 6); + } + } + currentPieceIsLaser = true; + SetRogueDemoCurrentPiece(0, 1, 3, 4); + ClearColumnAt(5); + ApplyBoardGravity(); + break; + + case DEMO_CROSS: + FillRogueDemoRows(13, 17, 5); + for (int y = 6; y < nGameHeight; y++) + { + FillRogueDemoCell(y, 4, 3); + } + currentPieceIsCross = true; + SetRogueDemoCurrentPiece(1, 0, 3, 8); + ClearRowAt(14); + ClearColumnAtWithColor(4, RGB(196, 255, 132)); + ApplyBoardGravity(); + break; + + case DEMO_RAINBOW: + FillRogueDemoRows(10, 16, 2); + for (int x = 0; x < nGameWidth; x++) + { + workRegion[13][x] = (x < 6) ? 3 : 5; + } + currentPieceIsRainbow = true; + SetRogueDemoCurrentPiece(1, 0, 3, 8); + { + int recoloredCount = 0; + TriggerRainbowColorShift(13, 11, 15, recoloredCount); + } + ApplyBoardGravity(); + break; + + case DEMO_VOID_CORE: + rogueStats.blackHoleLevel = 1; + rogueStats.blackHoleCharges = 1; + rogueStats.voidCoreLevel = 1; + FillRogueDemoRows(8, 19, 6); + for (int y = 8; y < nGameHeight; y++) + { + for (int x = 1; x < nGameWidth; x += 3) + { + workRegion[y][x] = 6; + } + } + UseBlackHole(); + SetFeedbackMessage(_T("虚空核心"), _T("黑洞吞噬后,下一枚特殊方块将变为彩虹方块。"), 12); + break; + + case DEMO_SWEEPER: + rogueStats.sweeperLevel = 4; + FillRogueDemoRows(13, 19, 1); + ClearRowAt(GetRoguePlayableHeight() - 1); + ApplyBoardGravity(); + SetFeedbackMessage(_T("底线清道夫"), _T("清道夫完成充能,自动扫掉底部压力行。"), 12); + break; + + case DEMO_LAST_CHANCE: + rogueStats.lastChanceUpgradeLevel = 1; + rogueStats.lastChanceCount = 1; + FillRogueDemoRows(2, 7, 5); + FillRogueDemoRows(14, 19, 3); + for (int i = 0; i < 3; i++) + { + DeleteOneLine(GetRoguePlayableHeight() - 1); + } + TriggerLineClearEffect(nullptr, 0, 3); + SetFeedbackMessage(_T("最后一搏"), _T("触顶前自动清除底部 3 行,保留继续操作空间。"), 12); + break; + + case DEMO_TIME_DILATION: + rogueStats.timeDilationLevel = 1; + rogueStats.timeDilationTicks = 8; + FillRogueDemoRows(3, 19, 4); + currentFallInterval = GetRogueFallInterval(); + SetFeedbackMessage(_T("时间缓流"), _T("堆叠高度危险,短时间内下落速度降低。"), 12); + break; + + case DEMO_UPGRADE_SHOCKWAVE: + rogueStats.upgradeShockwaveLevel = 1; + FillRogueDemoRows(12, 19, 2); + TriggerUpgradeShockwave(2); + ApplyBoardGravity(); + SetFeedbackMessage(_T("升级冲击波"), _T("升级完成后,冲击波清除底部 2 行。"), 12); + break; + + case DEMO_BLOCK_STORM: + rogueStats.blockStormLevel = 1; + rogueStats.blockStormPiecesRemaining = 5; + FillRogueDemoRows(15, 19, 0); + SetRogueDemoCurrentPiece(0, 0, 3, 2); + nextTypes[0] = 0; + nextTypes[1] = 0; + nextTypes[2] = 0; + SetFeedbackMessage(_T("方块风暴"), _T("当前和后续预览全部变为 I 块,方便制造四消。"), 12); + break; + + case DEMO_STABLE_STRUCTURE: + rogueStats.stableStructureLevel = 4; + FillRogueDemoRows(14, 19, 1); + workRegion[13][3] = 2; + workRegion[15][4] = 0; + workRegion[16][6] = 0; + TryStabilizeBoard(); + SetFeedbackMessage(_T("稳定结构"), _T("自动填补局部空洞,让堆叠更稳。"), 12); + break; + + default: + break; + } + + ComputeTarget(); +}