From 71a3684ce1d4d74d50fd208493e989bf0910f2ed Mon Sep 17 00:00:00 2001 From: Qi-huanye <2728290997@qq.com> Date: Sat, 25 Apr 2026 16:36:20 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E5=BD=A9=E8=99=B9=E6=96=B9?= =?UTF-8?q?=E5=9D=97=E4=B8=8E=E8=99=9A=E7=A9=BA=E6=A0=B8=E5=BF=83=E5=BC=BA?= =?UTF-8?q?=E5=8C=96=E9=93=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/include/Tetris.h | 4 + src/source/TetrisLogic.cpp | 222 ++++++++++++++++++++++++++++++++++-- src/source/TetrisRender.cpp | 41 ++++++- 强化TODO.md | 16 +-- 4 files changed, 265 insertions(+), 18 deletions(-) diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 2dbe53f..9a6657a 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -87,6 +87,9 @@ struct PlayerStats int blackHoleCharges; int reshapeLevel; int reshapeCharges; + int rainbowPieceLevel; + int voidCoreLevel; + int pendingRainbowPieceCount; int stableStructureLevel; int doubleGrowthLevel; int gamblerLevel; @@ -170,6 +173,7 @@ extern bool holdUsedThisTurn; extern bool currentPieceIsExplosive; extern bool currentPieceIsLaser; extern bool currentPieceIsCross; +extern bool currentPieceIsRainbow; extern int bricks[7][4][4][4]; extern COLORREF BrickColor[7]; diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 2095ea4..dfb13ee 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -25,6 +25,7 @@ bool holdUsedThisTurn = false; bool currentPieceIsExplosive = false; bool currentPieceIsLaser = false; bool currentPieceIsCross = false; +bool currentPieceIsRainbow = false; Point pendingChainBombCenter = { 0, 0 }; bool pendingChainBombFollowup = false; @@ -67,7 +68,9 @@ enum UpgradeId UPGRADE_BLOCK_STORM = 34, UPGRADE_CROSS_PIECE = 35, UPGRADE_BLACK_HOLE = 36, - UPGRADE_AIR_RESHAPE = 37 + UPGRADE_AIR_RESHAPE = 37, + UPGRADE_RAINBOW_PIECE = 38, + UPGRADE_VOID_CORE = 39 }; static const UpgradeEntry kUpgradePool[] = @@ -106,6 +109,8 @@ static const UpgradeEntry kUpgradePool[] = { UPGRADE_CROSS_PIECE, -1, 76, true, _T("\u5341\u5b57\u65b9\u5757"), _T("\u723d\u611f"), _T("\u63d0\u9ad8\u5341\u5b57\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u540e\u6e05\u9664\u6240\u5728\u884c\u4e0e\u6240\u5728\u5217\u3002") }, { UPGRADE_BLACK_HOLE, 1, 78, false, _T("\u9ed1\u6d1e"), _T("\u7279\u6b8a"), _T("\u83b7\u5f97 1 \u6b21\u4e3b\u52a8\u9ed1\u6d1e\uff0c\u91ca\u653e\u540e\u6e05\u9664\u68cb\u76d8\u4e0a\u6570\u91cf\u6700\u591a\u7684\u4e00\u79cd\u65b9\u5757\u3002") }, { UPGRADE_AIR_RESHAPE, 1, 74, false, _T("\u7a7a\u4e2d\u6362\u5f62"), _T("\u64cd\u4f5c"), _T("\u83b7\u5f97 1 \u6b21\u4e3b\u52a8\u6362\u5f62\uff0c\u53ef\u5c06\u5f53\u524d\u65b9\u5757\u53d8\u4e3a I \u5757\u3002") }, + { UPGRADE_RAINBOW_PIECE, 1, 74, false, _T("\u5f69\u8679\u65b9\u5757"), _T("\u723d\u611f"), _T("\u6982\u7387\u751f\u6210\u5f69\u8679\u65b9\u5757\uff0c\u843d\u5730\u540e\u53ef\u81ea\u52a8\u8865\u5168\u884c\u5185\u7f3a\u53e3\uff0c\u66f4\u5bb9\u6613\u8865\u9f50\u6d88\u884c\u3002") }, + { UPGRADE_VOID_CORE, 1, 112, false, _T("\u865a\u7a7a\u6838\u5fc3"), _T("\u8fdb\u5316"), _T("\u9ed1\u6d1e\u540e\u989d\u5916\u751f\u6210 1 \u4e2a\u5f69\u8679\u65b9\u5757\uff0c\u5f69\u8679\u6d88\u884c\u65f6\u89e6\u53d1\u5c0f\u578b\u9ed1\u6d1e\u3002") }, { UPGRADE_STABLE_STRUCTURE, -1, 72, true, _T("\u7a33\u5b9a\u7ed3\u6784"), _T("\u7279\u6b8a"), _T("\u843d\u5730\u540e\u5c0f\u6982\u7387\u81ea\u52a8\u586b\u8865\u90bb\u8fd1\u7a7a\u6d1e\uff0c\u63d0\u9ad8\u76d8\u9762\u7ed3\u6784\u7a33\u5b9a\u6027\u3002") }, { UPGRADE_DOUBLE_GROWTH, 1, 84, false, _T("\u6210\u957f\u6838\u5fc3"), _T("\u6210\u957f"), _T("\u6c38\u4e45\u83b7\u5f97 +15% \u5f97\u5206\u4e0e +15% EXP\uff0c\u53ea\u80fd\u9009\u62e9\u4e00\u6b21\u3002") }, { UPGRADE_PIECE_TUNING, -1, 64, true, _T("\u65b9\u5757\u6539\u9020"), _T("\u7279\u6b8a"), _T("\u9009\u62e9\u4e00\u79cd\u65b9\u5757\uff0c\u964d\u4f4e\u5176\u540e\u7eed\u51fa\u73b0\u6982\u7387\u3002") }, @@ -298,6 +303,9 @@ static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) stats.blackHoleCharges = 0; stats.reshapeLevel = 0; stats.reshapeCharges = 0; + stats.rainbowPieceLevel = 0; + stats.voidCoreLevel = 0; + stats.pendingRainbowPieceCount = 0; stats.stableStructureLevel = 0; stats.doubleGrowthLevel = 0; stats.gamblerLevel = 0; @@ -399,6 +407,10 @@ static int GetUpgradeCurrentLevel(int upgradeId) return rogueStats.blackHoleLevel; case UPGRADE_AIR_RESHAPE: return rogueStats.reshapeLevel; + case UPGRADE_RAINBOW_PIECE: + return rogueStats.rainbowPieceLevel; + case UPGRADE_VOID_CORE: + return rogueStats.voidCoreLevel; case UPGRADE_STABLE_STRUCTURE: return rogueStats.stableStructureLevel; case UPGRADE_DOUBLE_GROWTH: @@ -603,6 +615,15 @@ static int GetUpgradeDynamicWeight(const UpgradeEntry& entry) weight += 18; } break; + case UPGRADE_RAINBOW_PIECE: + if (boardIsDangerous || rogueStats.totalLinesCleared >= 8) + { + weight += 16; + } + break; + case UPGRADE_VOID_CORE: + weight += 30; + break; case UPGRADE_STABLE_STRUCTURE: if (boardIsDangerous) { @@ -744,6 +765,13 @@ static bool IsUpgradeSelectable(const UpgradeEntry& entry) rogueStats.controlMasterLevel == 0; } + if (entry.id == UPGRADE_VOID_CORE) + { + return rogueStats.blackHoleLevel > 0 && + rogueStats.rainbowPieceLevel > 0 && + rogueStats.voidCoreLevel == 0; + } + if (entry.repeatable) { return true; @@ -864,6 +892,70 @@ static bool RollCrossPiece() return (rand() % 100) < chancePercent; } +static bool RollRainbowPiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0) + { + return false; + } + + int chancePercent = 12; + return (rand() % 100) < chancePercent; +} + +static bool IsRainbowBoardCell(int cellValue) +{ + return cellValue == 8; +} + +static int TriggerMiniBlackHole(int maxCellsToClear) +{ + int blockCounts[8] = { 0 }; + + for (int y = 0; y < nGameHeight; y++) + { + for (int x = 0; x < nGameWidth; x++) + { + int cell = workRegion[y][x]; + if (cell >= 1 && cell <= 7) + { + blockCounts[cell]++; + } + } + } + + int targetBlock = 0; + int maxCount = 0; + for (int block = 1; block <= 7; block++) + { + if (blockCounts[block] > maxCount) + { + maxCount = blockCounts[block]; + targetBlock = block; + } + } + + if (targetBlock == 0 || maxCellsToClear <= 0) + { + return 0; + } + + int clearedCellCount = 0; + for (int y = nGameHeight - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--) + { + for (int x = 0; x < nGameWidth && clearedCellCount < maxCellsToClear; x++) + { + if (workRegion[y][x] == targetBlock) + { + workRegion[y][x] = 0; + clearedCellCount++; + } + } + } + + return clearedCellCount; +} + static void RollCurrentPieceSpecialFlags(bool allowRandomSpecials) { if (!allowRandomSpecials) @@ -871,12 +963,24 @@ static void RollCurrentPieceSpecialFlags(bool allowRandomSpecials) currentPieceIsExplosive = false; currentPieceIsLaser = false; currentPieceIsCross = false; + currentPieceIsRainbow = false; + return; + } + + if (currentMode == MODE_ROGUE && rogueStats.pendingRainbowPieceCount > 0) + { + rogueStats.pendingRainbowPieceCount--; + currentPieceIsExplosive = false; + currentPieceIsLaser = false; + currentPieceIsCross = false; + currentPieceIsRainbow = true; return; } currentPieceIsExplosive = RollExplosivePiece(); currentPieceIsLaser = !currentPieceIsExplosive && RollLaserPiece(); currentPieceIsCross = !currentPieceIsExplosive && !currentPieceIsLaser && RollCrossPiece(); + currentPieceIsRainbow = !currentPieceIsExplosive && !currentPieceIsLaser && !currentPieceIsCross && RollRainbowPiece(); } static int ClearExplosiveAreaAt(int centerY, int centerX) @@ -944,6 +1048,48 @@ static int ClearRowAt(int row) return clearedCellCount; } +static int TriggerRainbowRowCompletion(int minRow, int maxRow) +{ + int filledCellCount = 0; + + if (minRow < 0) + { + minRow = 0; + } + if (maxRow >= nGameHeight) + { + maxRow = nGameHeight - 1; + } + + for (int row = minRow; row <= maxRow; row++) + { + int emptyCount = 0; + int emptyColumn = -1; + bool hasRainbowCell = false; + + for (int x = 0; x < nGameWidth; x++) + { + if (workRegion[row][x] == 0) + { + emptyCount++; + emptyColumn = x; + } + else if (IsRainbowBoardCell(workRegion[row][x])) + { + hasRainbowCell = true; + } + } + + if (hasRainbowCell && emptyCount == 1 && emptyColumn >= 0) + { + workRegion[row][emptyColumn] = 8; + filledCellCount++; + } + } + + return filledCellCount; +} + static int TriggerBlackHole() { int blockCounts[8] = { 0 }; @@ -1511,6 +1657,12 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) rogueStats.reshapeLevel = 1; rogueStats.reshapeCharges++; 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; @@ -2099,6 +2251,7 @@ void Fixing() bool overflowTop = false; Point explosiveCells[4] = {}; int explosiveCellCount = 0; + int rainbowFilledCount = 0; pendingChainBombFollowup = false; for (int i = 0; i < 4; i++) @@ -2119,7 +2272,7 @@ void Fixing() // 将当前方块在可视区域内的部分写入工作区 if (fixY >= 0 && fixY < nGameHeight && fixX >= 0 && fixX < nGameWidth) { - workRegion[fixY][fixX] = bricks[type][state][i][j]; + workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; if (currentPieceIsExplosive && explosiveCellCount < 4) { explosiveCells[explosiveCellCount].x = fixX; @@ -2131,6 +2284,17 @@ void Fixing() } } + if (!overflowTop && currentPieceIsRainbow) + { + rainbowFilledCount = TriggerRainbowRowCompletion(point.y, point.y + 3); + if (rainbowFilledCount > 0) + { + TCHAR rainbowDetail[128]; + _stprintf_s(rainbowDetail, _T("\u8865\u5168 %d \u4e2a\u884c\u5185\u7f3a\u53e3\uff0c\u66f4\u5bb9\u6613\u8fbe\u6210\u6d88\u884c\u3002"), rainbowFilledCount); + SetFeedbackMessage(_T("\u5f69\u8679\u65b9\u5757\u751f\u6548"), rainbowDetail, 10); + } + } + if (overflowTop) { if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) @@ -2332,6 +2496,7 @@ void DeleteOneLine(int number) int DeleteLines() { int clearedLines = 0; + bool clearedWithRainbow = false; for (int i = nGameHeight - 1; i >= 0; i--) { @@ -2348,6 +2513,14 @@ int DeleteLines() if (fullLine) { + for (int j = 0; j < nGameWidth; j++) + { + if (IsRainbowBoardCell(workRegion[i][j])) + { + clearedWithRainbow = true; + break; + } + } DeleteOneLine(i); clearedLines++; i++; @@ -2400,6 +2573,25 @@ int DeleteLines() pendingChainBombFollowup = false; } + if (currentMode == MODE_ROGUE && clearedWithRainbow && rogueStats.voidCoreLevel > 0) + { + int miniBlackHoleCleared = TriggerMiniBlackHole(5); + if (miniBlackHoleCleared > 0) + { + int miniScore = miniBlackHoleCleared * rogueStats.scoreMultiplierPercent / 100; + if (miniScore < miniBlackHoleCleared) + { + miniScore = miniBlackHoleCleared; + } + rogueStats.score += miniScore; + tScore = rogueStats.score; + + TCHAR miniDetail[128]; + _stprintf_s(miniDetail, _T("\u5f69\u8679\u6d88\u884c\u89e6\u53d1\u5c0f\u578b\u9ed1\u6d1e\uff0c\u6e05\u9664 %d \u683c +%d Score"), miniBlackHoleCleared, miniScore); + SetFeedbackMessage(_T("\u865a\u7a7a\u6838\u5fc3\u89e6\u53d1"), miniDetail, 12); + } + } + return clearedLines; } @@ -2729,12 +2921,28 @@ void UseBlackHole() rogueStats.score += scoreGain; tScore = rogueStats.score; + if (rogueStats.voidCoreLevel > 0) + { + rogueStats.pendingRainbowPieceCount++; + } + TCHAR detail[128]; - _stprintf_s( - detail, - _T("吞噬数量最多的一种方块,清除 %d 格 +%d Score"), - clearedCells, - scoreGain); + if (rogueStats.voidCoreLevel > 0) + { + _stprintf_s( + detail, + _T("吞噬数量最多的一种方块,清除 %d 格 +%d Score 并生成 1 个彩虹方块"), + clearedCells, + scoreGain); + } + else + { + _stprintf_s( + detail, + _T("吞噬数量最多的一种方块,清除 %d 格 +%d Score"), + clearedCells, + scoreGain); + } SetFeedbackMessage(_T("主动释放黑洞"), detail, 12); currentFallInterval = GetRogueFallInterval(); diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index bce6ff0..d386a70 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -454,7 +454,9 @@ void TDrawScreen(HDC hdc, HWND hWnd) { if (workRegion[i][j] != 0) { - int colorIndex = workRegion[i][j] - 1; + int cellValue = workRegion[i][j]; + bool isRainbowCell = (cellValue == 8); + int colorIndex = isRainbowCell ? 0 : (cellValue - 1); RECT brickRect = { gameRect.left + j * grid + SS(2), @@ -463,8 +465,10 @@ void TDrawScreen(HDC hdc, HWND hWnd) gameRect.top + (i + 1) * grid - SS(2) }; - HBRUSH brickBrush = CreateSolidBrush(BrickColor[colorIndex]); - HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 248, 250)); + COLORREF fillColor = isRainbowCell ? RGB(255, 214, 120) : BrickColor[colorIndex]; + COLORREF borderColor = isRainbowCell ? RGB(255, 248, 191) : RGB(255, 248, 250); + HBRUSH brickBrush = CreateSolidBrush(fillColor); + HPEN brickPen = CreatePen(PS_SOLID, isRainbowCell ? SS(2) : 1, borderColor); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(10), SS(10)); @@ -549,6 +553,11 @@ void TDrawScreen(HDC hdc, HWND hWnd) activeOutlineColor = RGB(196, 255, 132); activeOutlineWidth = SS(3); } + else if (currentPieceIsRainbow) + { + activeOutlineColor = RGB(255, 226, 126); + activeOutlineWidth = SS(3); + } HPEN brickPen = CreatePen(PS_SOLID, activeOutlineWidth, activeOutlineColor); oldPen = (HPEN)SelectObject(hdc, brickPen); oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); @@ -724,6 +733,17 @@ void TDrawScreen(HDC hdc, HWND hWnd) TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(178), crossText, lstrlen(crossText)); } + if (rogueStats.rainbowPieceLevel > 0) + { + TCHAR rainbowText[128]; + _stprintf_s( + rainbowText, + _T("\u5f69\u8679\u6982\u7387 12%% %s"), + currentPieceIsRainbow ? _T("\u672c\u5757\u5df2\u5f69\u8679") : + (rogueStats.pendingRainbowPieceCount > 0 ? _T("\u4e0b\u4e00\u5757\u5f69\u8679") : _T("\u672c\u5757\u666e\u901a"))); + TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(210), rainbowText, lstrlen(rainbowText)); + } + if (rogueStats.laserLevel > 0) { TCHAR laserText[96]; @@ -786,6 +806,13 @@ void TDrawScreen(HDC hdc, HWND hWnd) TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(850), reshapeText, lstrlen(reshapeText)); } + if (rogueStats.voidCoreLevel > 0) + { + TCHAR voidText[128]; + _stprintf_s(voidText, _T("\u865a\u7a7a\u6838\u5fc3 \u9ed1\u6d1e\u540e\u989d\u5916\u751f\u6210\u5f69\u8679\uff0c\u5f69\u8679\u6d88\u884c\u89e6\u53d1\u5c0f\u578b\u9ed1\u6d1e")); + TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(882), voidText, lstrlen(voidText)); + } + if (rogueStats.terminalClearLevel > 0) { TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(562), _T("\u7ec8\u672b\u6e05\u573a \u6fc0\u6d3b\u6700\u540e\u4e00\u640f\u65f6\u81ea\u52a8\u89e6\u53d1"), lstrlen(_T("\u7ec8\u672b\u6e05\u573a \u6fc0\u6d3b\u6700\u540e\u4e00\u640f\u65f6\u81ea\u52a8\u89e6\u53d1"))); @@ -1026,6 +1053,14 @@ void TDrawScreen(HDC hdc, HWND hWnd) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a7a\u4e2d\u6362\u5f62 Lv.1\r\n")); } + if (rogueStats.rainbowPieceLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u5f69\u8679\u65b9\u5757 Lv.1\r\n")); + } + if (rogueStats.voidCoreLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u865a\u7a7a\u6838\u5fc3 Lv.1\r\n")); + } if (rogueStats.stableStructureLevel > 0) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u7a33\u5b9a\u7ed3\u6784 Lv.%d\r\n"), rogueStats.stableStructureLevel); diff --git a/强化TODO.md b/强化TODO.md index ef23301..6ac5e41 100644 --- a/强化TODO.md +++ b/强化TODO.md @@ -11,9 +11,9 @@ | 状态 | 数量 | |---|---:| -| 已完成 | 28 | +| 已完成 | 30 | | 部分完成 | 5 | -| 未完成 | 2 | +| 未完成 | 0 | ## 已完成 @@ -35,6 +35,7 @@ | 升级冲击波 | 爽感 | 每次升级时自动清除底部 2 行 | 积分倍率 + 经验强化 + 减压 | | 黑洞 | 爽感 | 获得有限次数主动技能,释放后清除棋盘中数量最多的一种方块 | 基础出现 | | 方块风暴 | 爽感 | 接下来 5 个方块全部变成 I 方块 | 基础出现 | +| 彩虹方块 | 爽感 | 概率生成彩虹方块,落地后可自动补全所在行的单格缺口,更容易补齐消行 | 基础出现 | | 完美旋转 | 操作 | 旋转失败时自动尝试左右各偏移 1 格修正 | 基础出现 | | 空中换形 | 操作 | 获得有限次数主动技能,可将当前方块变为 I 方块 | 基础出现 | | 赌徒 | 风险 | 选强化时概率双倍生效,也可能落空 | 基础出现 | @@ -47,6 +48,7 @@ | 终末清场 | 进化 | 触发最后一搏时自动释放一次清屏炸弹,并进入狂热 | 清屏炸弹 + 最后一搏 | | 操控大师 | 进化 | Hold 后短时间减速、旋转失败自动修正、增加一个预览 | 完美旋转 + 暂存槽 | | 命运轮盘 | 进化 | 升级出现 5 个选项,可选 2 个,其中 1 个附带诅咒 | 赌徒 + 双重选择 | +| 虚空核心 | 进化 | 黑洞后额外生成一个彩虹方块;彩虹方块参与消行时触发一次小型黑洞 | 黑洞 + 彩虹方块 | ## 部分完成 @@ -62,8 +64,6 @@ | 强化 | 分类 | 目标效果 | 升级路径 | |---|---|---|---| -| 彩虹方块 | 爽感 | 可替代任意方块格子,用于补齐消行 | 基础出现 | -| 虚空核心 | 进化 | 黑洞和彩虹方块互相触发,小型黑洞追加清除 | 黑洞 + 彩虹方块 | ## 备注 @@ -75,7 +75,7 @@ ## 推荐下一批实现顺序 -1. `彩虹方块 + 虚空核心` -2. `炸弹方块` 出现机制对齐为“每隔 10 个方块” -3. `狂热模式` 补上“特殊方块出现率提高” -4. `极限玩家` 补上“30 秒未四消增加危险等级” +1. `炸弹方块` 出现机制对齐为“每隔 10 个方块” +2. `狂热模式` 补上“特殊方块出现率提高” +3. `极限玩家` 补上“30 秒未四消增加危险等级” +4. `时间缓流 / 赌命四消` 的数值与触发条件继续向文档收敛