From 30ccac8cbd2a1e0574f95c5c4b86007b9dda08e9 Mon Sep 17 00:00:00 2001 From: Qi-huanye <2728290997@qq.com> Date: Sun, 26 Apr 2026 09:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=9A=BE=E5=BA=A6=E4=B8=8A?= =?UTF-8?q?=E5=8D=87=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/include/Tetris.h | 6 ++ src/source/Tetris.cpp | 15 ++++- src/source/TetrisLogic.cpp | 20 +++--- src/source/TetrisRender.cpp | 63 ++++++++++++++++-- src/source/TetrisRogue.cpp | 129 ++++++++++++++++++++++++++++++------ 5 files changed, 198 insertions(+), 35 deletions(-) diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 3be0caf..909b8d4 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -97,6 +97,9 @@ struct PlayerStats int stableStructureLevel; int doubleGrowthLevel; int gamblerLevel; + int difficultyElapsedMs; + int difficultyLevel; + int lockedRows; int pieceTuningLevels[7]; }; @@ -204,5 +207,8 @@ void UseScreenBomb(); void UseBlackHole(); void UseAirReshape(); int GetRogueFallInterval(); +int GetRoguePlayableHeight(); +int GetRogueLockedRows(); +void AdvanceRogueDifficulty(int elapsedMs); void TDrawScreen(HDC hdc, HWND hWnd); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index aa602ab..bbe2b3c 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -221,10 +221,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) !suspendFlag && !gameOverFlag) { + if (currentMode == MODE_ROGUE) + { + int previousFallInterval = currentFallInterval; + AdvanceRogueDifficulty(currentFallInterval > 0 ? currentFallInterval : GAME_TIMER_INTERVAL); + if (currentFallInterval != previousFallInterval) + { + ResetGameTimer(hWnd); + } + } + if (currentMode == MODE_ROGUE && rogueStats.timeDilationLevel > 0 && rogueStats.timeDilationTicks <= 0) { int occupiedHeight = 0; - for (int y = 0; y < nGameHeight; y++) + int playableHeight = GetRoguePlayableHeight(); + for (int y = 0; y < playableHeight; y++) { bool hasCell = false; for (int x = 0; x < nGameWidth; x++) @@ -237,7 +248,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } if (hasCell) { - occupiedHeight = nGameHeight - y; + occupiedHeight = playableHeight - y; break; } } diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 3bb2840..c276b7f 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -223,6 +223,9 @@ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) stats.stableStructureLevel = 0; stats.doubleGrowthLevel = 0; stats.gamblerLevel = 0; + stats.difficultyElapsedMs = 0; + stats.difficultyLevel = 0; + stats.lockedRows = 0; for (int i = 0; i < 7; i++) { stats.pieceTuningLevels[i] = 0; @@ -250,7 +253,7 @@ bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) int checkY = position.y + i; int checkX = position.x + j; - if (checkX < 0 || checkX >= nGameWidth || checkY >= nGameHeight) + if (checkX < 0 || checkX >= nGameWidth || checkY >= GetRoguePlayableHeight()) { return false; } @@ -288,7 +291,7 @@ bool CanMoveDown() int nextX = point.x + j; // 检查是否到达底部边界 - if (nextY >= nGameHeight) + if (nextY >= GetRoguePlayableHeight()) { return false; } @@ -511,7 +514,7 @@ void Fixing() } // 将当前方块在可视区域内的部分写入工作区 - if (fixY >= 0 && fixY < nGameHeight && fixX >= 0 && fixX < nGameWidth) + if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth) { workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; if (currentPieceIsExplosive && explosiveCellCount < 4) @@ -560,7 +563,7 @@ void Fixing() for (int i = 0; i < 3; i++) { - DeleteOneLine(nGameHeight - 1); + DeleteOneLine(GetRoguePlayableHeight() - 1); } SetFeedbackMessage( @@ -643,9 +646,9 @@ void Fixing() { crossRow = 0; } - if (crossRow >= nGameHeight) + if (crossRow >= GetRoguePlayableHeight()) { - crossRow = nGameHeight - 1; + crossRow = GetRoguePlayableHeight() - 1; } if (crossColumn < 0) { @@ -739,7 +742,8 @@ int DeleteLines() int clearedLines = 0; bool clearedWithRainbow = false; - for (int i = nGameHeight - 1; i >= 0; i--) + int playableHeight = GetRoguePlayableHeight(); + for (int i = playableHeight - 1; i >= 0; i--) { bool fullLine = true; @@ -782,7 +786,7 @@ int DeleteLines() { for (int x = centerX - 1; x <= centerX + 1; x++) { - if (y >= 0 && y < nGameHeight && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) + if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) { workRegion[y][x] = 0; followupCleared++; diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index f42b89f..de3e063 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -629,6 +629,45 @@ void TDrawScreen(HDC hdc, HWND hWnd) SelectObject(hdc, oldPen); DeleteObject(gridPen); + int lockedRows = GetRogueLockedRows(); + if (lockedRows > 0) + { + RECT lockedRect = + { + gameRect.left, + gameRect.top + (nGameHeight - lockedRows) * grid, + gameRect.right, + gameRect.bottom + }; + + Graphics lockedGraphics(hdc); + SolidBrush lockedBrush(Color(150, 58, 48, 68)); + Pen lockedPen(Color(210, 232, 170, 194), 1.0f); + lockedGraphics.FillRectangle( + &lockedBrush, + static_cast(lockedRect.left), + static_cast(lockedRect.top), + static_cast(lockedRect.right - lockedRect.left), + static_cast(lockedRect.bottom - lockedRect.top)); + lockedGraphics.DrawRectangle( + &lockedPen, + static_cast(lockedRect.left), + static_cast(lockedRect.top), + static_cast(lockedRect.right - lockedRect.left), + static_cast(lockedRect.bottom - lockedRect.top)); + + SelectObject(hdc, smallFont); + SetTextColor(hdc, RGB(238, 204, 216)); + RECT lockedTextRect = + { + lockedRect.left + SS(12), + lockedRect.top + SS(8), + lockedRect.right - SS(12), + lockedRect.bottom - SS(8) + }; + DrawText(hdc, _T("\u96be\u5ea6\u538b\u7f29\u533a"), -1, &lockedTextRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } + for (int i = 0; i < nGameHeight; i++) { for (int j = 0; j < nGameWidth; j++) @@ -783,12 +822,24 @@ void TDrawScreen(HDC hdc, HWND hWnd) SelectObject(hdc, bodyFont); TCHAR overviewText[160]; int totalLines = (currentMode == MODE_CLASSIC) ? classicStats.totalLinesCleared : rogueStats.totalLinesCleared; - _stprintf_s( - overviewText, - _T("\u5f53\u524d\u5f97\u5206 %d\r\n\u5f53\u524d\u6a21\u5f0f %s\r\n\u7d2f\u8ba1\u6d88\u884c %d"), - tScore, - currentMode == MODE_CLASSIC ? _T("\u7ecf\u5178") : _T("Rogue"), - totalLines); + if (currentMode == MODE_ROGUE) + { + _stprintf_s( + overviewText, + _T("\u5f53\u524d\u5f97\u5206 %d\r\n\u5f53\u524d\u6a21\u5f0f Rogue\r\n\u7d2f\u8ba1\u6d88\u884c %d\r\n\u96be\u5ea6 Lv.%d \u9501\u884c %d"), + tScore, + totalLines, + rogueStats.difficultyLevel, + GetRogueLockedRows()); + } + else + { + _stprintf_s( + overviewText, + _T("\u5f53\u524d\u5f97\u5206 %d\r\n\u5f53\u524d\u6a21\u5f0f \u7ecf\u5178\r\n\u7d2f\u8ba1\u6d88\u884c %d"), + tScore, + totalLines); + } RECT overviewBodyRect = { overviewRect.left + SS(22), diff --git a/src/source/TetrisRogue.cpp b/src/source/TetrisRogue.cpp index 5ad7fb1..3dab2e9 100644 --- a/src/source/TetrisRogue.cpp +++ b/src/source/TetrisRogue.cpp @@ -90,6 +90,10 @@ static const UpgradeEntry kUpgradePool[] = }; static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]); +static constexpr int kDifficultyStepMs = 30000; +static constexpr int kDifficultySpeedStepMs = 18; +static constexpr int kMaxRogueLockedRows = 4; +static constexpr int kDifficultyLevelsPerLockedRow = 3; static int GetUpgradeCurrentLevel(int upgradeId); static int GetUpgradeDynamicWeight(const UpgradeEntry& entry); @@ -111,6 +115,7 @@ static int TriggerUpgradeShockwave(int rowsToClear); static void FillUpgradeOptions(); static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount); static void ApplyDestinyCurse(); +static void ClearLockedRows(); static int GetNextPreviewLimit() { @@ -132,6 +137,91 @@ static int GetNextPreviewLimit() return rogueStats.previewCount; } +int GetRogueLockedRows() +{ + if (currentMode != MODE_ROGUE) + { + return 0; + } + + if (rogueStats.lockedRows < 0) + { + return 0; + } + + return rogueStats.lockedRows > kMaxRogueLockedRows ? kMaxRogueLockedRows : rogueStats.lockedRows; +} + +int GetRoguePlayableHeight() +{ + return nGameHeight - GetRogueLockedRows(); +} + +static void ClearLockedRows() +{ + int lockedRows = GetRogueLockedRows(); + for (int y = nGameHeight - lockedRows; y < nGameHeight; y++) + { + if (y < 0 || y >= nGameHeight) + { + continue; + } + + for (int x = 0; x < nGameWidth; x++) + { + workRegion[y][x] = 0; + } + } +} + +void AdvanceRogueDifficulty(int elapsedMs) +{ + if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag || elapsedMs <= 0) + { + return; + } + + rogueStats.difficultyElapsedMs += elapsedMs; + bool difficultyChanged = false; + + while (rogueStats.difficultyElapsedMs >= kDifficultyStepMs) + { + rogueStats.difficultyElapsedMs -= kDifficultyStepMs; + rogueStats.difficultyLevel++; + difficultyChanged = true; + } + + if (!difficultyChanged) + { + return; + } + + int nextLockedRows = rogueStats.difficultyLevel / kDifficultyLevelsPerLockedRow; + if (nextLockedRows > kMaxRogueLockedRows) + { + nextLockedRows = kMaxRogueLockedRows; + } + + bool lockedRowsChanged = nextLockedRows > rogueStats.lockedRows; + if (lockedRowsChanged) + { + rogueStats.lockedRows = nextLockedRows; + ClearLockedRows(); + ComputeTarget(); + } + + currentFallInterval = GetRogueFallInterval(); + + TCHAR difficultyDetail[128]; + _stprintf_s( + difficultyDetail, + _T("\u96be\u5ea6 Lv.%d\uff0c\u4e0b\u843d\u52a0\u5feb\uff0c\u5df2\u9501\u5b9a\u5e95\u90e8 %d/%d \u884c\u3002"), + rogueStats.difficultyLevel, + GetRogueLockedRows(), + kMaxRogueLockedRows); + SetFeedbackMessage(lockedRowsChanged ? _T("\u68cb\u76d8\u538b\u7f29") : _T("\u96be\u5ea6\u4e0a\u5347"), difficultyDetail, 12); +} + static int GetUpgradeCurrentLevel(int upgradeId) { switch (upgradeId) @@ -228,7 +318,7 @@ static int GetUpgradeDynamicWeight(const UpgradeEntry& entry) int topRow = GetTopOccupiedRow(); if (topRow >= 0) { - int occupiedHeight = nGameHeight - topRow; + int occupiedHeight = GetRoguePlayableHeight() - topRow; if (occupiedHeight >= 14) { weight += 50; @@ -362,7 +452,7 @@ static bool IsUpgradeSelectable(const UpgradeEntry& entry) static int GetTopOccupiedRow() { - for (int i = 0; i < nGameHeight; i++) + for (int i = 0; i < GetRoguePlayableHeight(); i++) { for (int j = 0; j < nGameWidth; j++) { @@ -478,7 +568,7 @@ int TriggerMiniBlackHole(int maxCellsToClear) { int blockCounts[8] = { 0 }; - for (int y = 0; y < nGameHeight; y++) + for (int y = 0; y < GetRoguePlayableHeight(); y++) { for (int x = 0; x < nGameWidth; x++) { @@ -507,7 +597,7 @@ int TriggerMiniBlackHole(int maxCellsToClear) } int clearedCellCount = 0; - for (int y = nGameHeight - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--) + for (int y = GetRoguePlayableHeight() - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--) { for (int x = 0; x < nGameWidth && clearedCellCount < maxCellsToClear; x++) { @@ -558,7 +648,7 @@ int ClearExplosiveAreaAt(int centerY, int centerX) { for (int x = centerX - radius; x <= centerX + radius; x++) { - if (y >= 0 && y < nGameHeight && x >= 0 && x < nGameWidth) + if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth) { if (workRegion[y][x] != 0) { @@ -581,7 +671,7 @@ int ClearColumnAt(int column) return 0; } - for (int y = 0; y < nGameHeight; y++) + for (int y = 0; y < GetRoguePlayableHeight(); y++) { if (workRegion[y][column] != 0) { @@ -597,7 +687,7 @@ int ClearRowAt(int row) { int clearedCellCount = 0; - if (row < 0 || row >= nGameHeight) + if (row < 0 || row >= GetRoguePlayableHeight()) { return 0; } @@ -622,9 +712,9 @@ int TriggerRainbowRowCompletion(int minRow, int maxRow) { minRow = 0; } - if (maxRow >= nGameHeight) + if (maxRow >= GetRoguePlayableHeight()) { - maxRow = nGameHeight - 1; + maxRow = GetRoguePlayableHeight() - 1; } for (int row = minRow; row <= maxRow; row++) @@ -659,7 +749,7 @@ int TriggerRainbowRowCompletion(int minRow, int maxRow) static int TriggerBlackHole() { int blockCounts[8] = { 0 }; - for (int y = 0; y < nGameHeight; y++) + for (int y = 0; y < GetRoguePlayableHeight(); y++) { for (int x = 0; x < nGameWidth; x++) { @@ -688,7 +778,7 @@ static int TriggerBlackHole() } int clearedCellCount = 0; - for (int y = 0; y < nGameHeight; y++) + for (int y = 0; y < GetRoguePlayableHeight(); y++) { for (int x = 0; x < nGameWidth; x++) { @@ -709,7 +799,7 @@ int TriggerScreenBomb() for (int i = 0; i < 5; i++) { - int row = nGameHeight - 1 - i; + int row = GetRoguePlayableHeight() - 1 - i; if (row < 0) { break; @@ -748,9 +838,9 @@ static int TriggerChainBlast(int lineAnchor) { randomY = 0; } - if (randomY >= nGameHeight) + if (randomY >= GetRoguePlayableHeight()) { - randomY = nGameHeight - 1; + randomY = GetRoguePlayableHeight() - 1; } if (workRegion[randomY][randomX] != 0) @@ -822,7 +912,7 @@ int TryStabilizeBoard() return 0; } - for (int y = nGameHeight - 2; y >= 1; y--) + for (int y = GetRoguePlayableHeight() - 2; y >= 1; y--) { for (int x = 1; x < nGameWidth - 1; x++) { @@ -913,7 +1003,7 @@ static int TriggerUpgradeShockwave(int rowsToClear) for (int i = 0; i < rowsToClear; i++) { - DeleteOneLine(nGameHeight - 1); + DeleteOneLine(GetRoguePlayableHeight() - 1); clearedRows++; } @@ -1027,6 +1117,7 @@ static void FillUpgradeOptions() int GetRogueFallInterval() { int baseInterval = 500 + rogueStats.slowFallStacks * 80; + baseInterval -= rogueStats.difficultyLevel * kDifficultySpeedStepMs; if (rogueStats.highPressureLevel > 0) { @@ -1388,7 +1479,7 @@ void ApplyLineClearResult(int linesCleared) int chainBlastCells = 0; for (int i = 0; i < linesCleared; i++) { - chainBlastCells += TriggerChainBlast(nGameHeight - 1 - i); + chainBlastCells += TriggerChainBlast(GetRoguePlayableHeight() - 1 - i); } if (chainBlastCells > 0) @@ -1416,7 +1507,7 @@ void ApplyLineClearResult(int linesCleared) int thunderRowsCleared = 0; for (int i = 0; i < 2; i++) { - int randomRow = rand() % nGameHeight; + int randomRow = rand() % GetRoguePlayableHeight(); for (int x = 0; x < nGameWidth; x++) { if (workRegion[randomRow][x] != 0) @@ -1485,7 +1576,7 @@ void ApplyLineClearResult(int linesCleared) while (rogueStats.sweeperCharge >= sweeperThreshold) { rogueStats.sweeperCharge -= sweeperThreshold; - DeleteOneLine(nGameHeight - 1); + DeleteOneLine(GetRoguePlayableHeight() - 1); clearedBySweeper++; }