diff --git a/build-mingw.ps1 b/build-mingw.ps1 index 94b1830..c362d9b 100644 --- a/build-mingw.ps1 +++ b/build-mingw.ps1 @@ -65,6 +65,7 @@ $Sources = @( (Join-Path $SourceDir "stdafx.cpp"), (Join-Path $SourceDir "Tetris.cpp"), (Join-Path $SourceDir "TetrisLogic.cpp"), + (Join-Path $SourceDir "TetrisRogue.cpp"), (Join-Path $SourceDir "TetrisRender.cpp") ) diff --git a/src/include/TetrisLogicInternal.h b/src/include/TetrisLogicInternal.h new file mode 100644 index 0000000..0af909f --- /dev/null +++ b/src/include/TetrisLogicInternal.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Tetris.h" + +extern Point pendingChainBombCenter; +extern bool pendingChainBombFollowup; + +Point GetSpawnPoint(int brickType); +void ResetPlayerStats(PlayerStats& stats, bool useRogueRules); +void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); +bool IsPiecePlacementValid(int pieceType, int pieceState, Point position); +bool IsRainbowBoardCell(int cellValue); +int TriggerMiniBlackHole(int maxCellsToClear); +int TriggerRainbowRowCompletion(int minRow, int maxRow); +int TriggerScreenBomb(); +int ClearExplosiveAreaAt(int centerY, int centerX); +int ClearColumnAt(int column); +int ClearRowAt(int row); +int TryStabilizeBoard(); +void RollCurrentPieceSpecialFlags(bool allowRandomSpecials); +void ResetNextQueue(); +int ConsumeNextType(); +void ApplyLineClearResult(int linesCleared); diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 1c340e9..3bb2840 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Tetris.h" +#include "TetrisLogicInternal.h" int nType = 0; int type = 0; @@ -29,97 +30,6 @@ bool currentPieceIsRainbow = false; Point pendingChainBombCenter = { 0, 0 }; bool pendingChainBombFollowup = false; -enum UpgradeId -{ - UPGRADE_SCORE_MULTIPLIER = 0, - UPGRADE_EXP_MULTIPLIER = 1, - UPGRADE_SLOW_FALL = 2, - UPGRADE_COMBO_BONUS = 3, - UPGRADE_PREVIEW_PLUS_ONE = 4, - UPGRADE_LAST_CHANCE = 5, - UPGRADE_HOLD_UNLOCK = 6, - UPGRADE_PRESSURE_RELIEF = 7, - UPGRADE_SWEEPER = 8, - UPGRADE_EXPLOSIVE_PIECE = 9, - UPGRADE_STABLE_STRUCTURE = 10, - UPGRADE_DOUBLE_GROWTH = 11, - UPGRADE_PIECE_TUNING = 12, - UPGRADE_GAMBLER = 13, - UPGRADE_CHAIN_BLAST = 14, - UPGRADE_CHAIN_BOMB = 15, - UPGRADE_LASER_PIECE = 16, - UPGRADE_THUNDER_TETRIS = 17, - UPGRADE_THUNDER_LASER = 18, - UPGRADE_FEVER_MODE = 19, - UPGRADE_RAGE_STACK = 20, - UPGRADE_INFINITE_FEVER = 21, - UPGRADE_SCREEN_BOMB = 22, - UPGRADE_TERMINAL_CLEAR = 23, - UPGRADE_DUAL_CHOICE = 24, - UPGRADE_DESTINY_WHEEL = 25, - UPGRADE_PERFECT_ROTATE = 26, - UPGRADE_TIME_DILATION = 27, - UPGRADE_HIGH_PRESSURE = 28, - UPGRADE_TETRIS_GAMBLE = 29, - UPGRADE_EXTREME_PLAYER = 30, - UPGRADE_UPGRADE_SHOCKWAVE = 31, - UPGRADE_EVOLUTION_IMPACT = 32, - UPGRADE_CONTROL_MASTER = 33, - UPGRADE_BLOCK_STORM = 34, - UPGRADE_CROSS_PIECE = 35, - UPGRADE_BLACK_HOLE = 36, - UPGRADE_AIR_RESHAPE = 37, - UPGRADE_RAINBOW_PIECE = 38, - UPGRADE_VOID_CORE = 39 -}; - -static const UpgradeEntry kUpgradePool[] = -{ - { UPGRADE_SCORE_MULTIPLIER, -1, 100, true, _T("\u5206\u6570\u500d\u7387"), _T("\u5f97\u5206"), _T("\u6240\u6709\u5f97\u5206\u63d0\u9ad8 20%\u3002") }, - { UPGRADE_COMBO_BONUS, -1, 95, true, _T("\u8fde\u51fb\u52a0\u6210"), _T("\u8282\u594f"), _T("\u8fde\u7eed\u6d88\u884c\u65f6\u8ffd\u52a0\u8fde\u51fb\u5956\u52b1\u3002") }, - { UPGRADE_SLOW_FALL, -1, 90, true, _T("\u6162\u901f\u4e0b\u843d"), _T("\u64cd\u4f5c"), _T("\u81ea\u7136\u4e0b\u843d\u53d8\u6162\uff0c\u6bcf\u6b21\u63d0\u9ad8 80ms\u3002") }, - { UPGRADE_PREVIEW_PLUS_ONE, 3, 85, false, _T("\u989d\u5916\u9884\u89c8"), _T("\u89c6\u91ce"), _T("\u4e0b\u4e00\u4e2a\u65b9\u5757\u9884\u89c8 +1\uff0c\u6700\u591a 3 \u4e2a\u3002") }, - { UPGRADE_EXP_MULTIPLIER, -1, 100, true, _T("\u7ecf\u9a8c\u5f3a\u5316"), _T("\u6210\u957f"), _T("\u540e\u7eed\u6d88\u884c\u83b7\u5f97 EXP \u63d0\u9ad8 25%\u3002") }, - { UPGRADE_LAST_CHANCE, 1, 72, false, _T("\u6700\u540e\u4e00\u640f"), _T("\u4fdd\u547d"), _T("\u9996\u6b21\u9876\u6b7b\u65f6\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 3 \u884c\u5e76\u7ee7\u7eed\u6e38\u620f\u3002") }, - { UPGRADE_HOLD_UNLOCK, 1, 78, false, _T("Hold \u89e3\u9501"), _T("\u7279\u6b8a"), _T("\u89e3\u9501 Hold \u69fd\uff0c\u5bf9\u5c40\u4e2d\u53ef\u7528 C \u6216 Shift \u6682\u5b58\u65b9\u5757\u3002") }, - { UPGRADE_PRESSURE_RELIEF, -1, 82, true, _T("\u51cf\u538b"), _T("\u7279\u6b8a"), _T("\u7acb\u5373\u6e05\u9664\u5f53\u524d\u6700\u9ad8\u5360\u7528\u884c\uff0c\u4e3a\u76d8\u9762\u817e\u51fa\u547c\u5438\u7a7a\u95f4\u3002") }, - { UPGRADE_SWEEPER, -1, 74, true, _T("\u6e05\u626b\u8005"), _T("\u7279\u6b8a"), _T("\u7d2f\u8ba1\u6d88\u884c\u5145\u80fd\uff0c\u6536\u6ee1\u540e\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 1 \u884c\u3002") }, - { UPGRADE_EXPLOSIVE_PIECE, -1, 76, true, _T("\u7206\u7834\u65b9\u5757"), _T("\u7279\u6b8a"), _T("\u63d0\u9ad8\u7206\u7834\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u65f6\u89e6\u53d1 3x3 \u6e05\u9664\u3002") }, - { UPGRADE_CHAIN_BLAST, 1, 92, false, _T("\u8fde\u9501\u7206\u7834"), _T("\u8fdb\u9636"), _T("\u6d88\u884c\u540e\u5bf9\u88ab\u6e05\u9664\u884c\u9644\u8fd1\u968f\u673a\u6e05\u6389\u51e0\u683c\u3002") }, - { UPGRADE_CHAIN_BOMB, 1, 110, false, _T("\u8fde\u73af\u70b8\u5f39"), _T("\u8fdb\u5316"), _T("\u7206\u7834\u8303\u56f4\u63d0\u5347\u4e3a 5x5\uff0c\u82e5\u5f15\u53d1\u6d88\u884c\u5219\u8ffd\u52a0\u4e00\u6b21\u5c0f\u7206\u70b8\u3002") }, - { UPGRADE_LASER_PIECE, -1, 72, true, _T("\u6fc0\u5149\u65b9\u5757"), _T("\u7279\u6b8a"), _T("\u63d0\u9ad8\u6fc0\u5149\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u540e\u6e05\u9664\u6240\u5728\u6574\u5217\u3002") }, - { UPGRADE_THUNDER_TETRIS, 1, 94, false, _T("\u96f7\u9706\u56db\u6d88"), _T("\u8fdb\u9636"), _T("\u6bcf\u6b21\u5b8c\u6210\u56db\u6d88\u65f6\uff0c\u989d\u5916\u6e05\u9664\u968f\u673a 2 \u884c\u3002") }, - { UPGRADE_THUNDER_LASER, 1, 112, false, _T("\u96f7\u9706\u6fc0\u5149"), _T("\u8fdb\u5316"), _T("\u56db\u6d88\u65f6\u989d\u5916\u53d1\u5c04 2 \u9053\u6fc0\u5149\uff0c\u968f\u673a\u6e05\u9664 2 \u5217\u5e76\u83b7\u5f97 EXP\u3002") }, - { UPGRADE_FEVER_MODE, 1, 84, false, _T("\u72c2\u70ed\u6a21\u5f0f"), _T("\u8fdb\u9636"), _T("\u7d2f\u8ba1\u6d88\u884c 20 \u884c\u540e\u8fdb\u5165 10 \u79d2\u72c2\u70ed\u72b6\u6001\u3002") }, - { UPGRADE_RAGE_STACK, 1, 84, false, _T("\u66b4\u8d70\u5806\u53e0"), _T("\u8fdb\u9636"), _T("\u8fde\u7eed\u6d88\u884c\u8d8a\u591a\uff0c\u5f97\u5206\u500d\u7387\u8ffd\u52a0\u8d8a\u9ad8\u3002") }, - { UPGRADE_INFINITE_FEVER, 1, 110, false, _T("\u65e0\u9650\u72c2\u70ed"), _T("\u8fdb\u5316"), _T("\u72c2\u70ed\u671f\u95f4\u6d88\u884c\u53ef\u5ef6\u957f\u65f6\u95f4\uff0c\u8fde\u51fb\u8d8a\u9ad8\u500d\u7387\u8d8a\u5f3a\u3002") }, - { UPGRADE_SCREEN_BOMB, 1, 78, false, _T("\u6e05\u5c4f\u70b8\u5f39"), _T("\u8fdb\u9636"), _T("\u7d2f\u8ba1\u6d88\u884c 30 \u884c\u540e\u83b7\u5f97 1 \u6b21\u6e05\u5c4f\u70b8\u5f39\uff0c\u53ef\u81ea\u52a8\u6216\u4e3b\u52a8\u6e05\u9664\u5e95\u90e8 5 \u884c\u3002") }, - { UPGRADE_TERMINAL_CLEAR, 1, 108, false, _T("\u7ec8\u672b\u6e05\u573a"), _T("\u8fdb\u5316"), _T("\u6fc0\u6d3b\u6700\u540e\u4e00\u640f\u65f6\u81ea\u52a8\u91ca\u653e\u4e00\u6b21\u6e05\u5c4f\u70b8\u5f39\uff0c\u5e76\u8fdb\u5165 10 \u79d2\u72c2\u70ed\u3002") }, - { UPGRADE_DUAL_CHOICE, 1, 68, false, _T("\u53cc\u91cd\u9009\u62e9"), _T("\u8fdb\u9636"), _T("\u6bcf\u6b21\u5347\u7ea7\u53ef\u989d\u5916\u518d\u9009 1 \u4e2a\u5f3a\u5316\uff0c\u4f46\u4e0b\u4e00\u6b21\u5347\u7ea7\u9700\u6c42 +30%\u3002") }, - { UPGRADE_DESTINY_WHEEL, 1, 104, false, _T("\u547d\u8fd0\u8f6e\u76d8"), _T("\u8fdb\u5316"), _T("\u6bcf\u6b21\u5347\u7ea7\u51fa\u73b0 5 \u4e2a\u9009\u9879\uff0c\u53ef\u9009 2 \u4e2a\uff0c\u4f46\u5176\u4e2d 1 \u4e2a\u9644\u5e26\u8d1f\u9762\u6548\u679c\u3002") }, - { UPGRADE_PERFECT_ROTATE, 1, 82, false, _T("\u5b8c\u7f8e\u65cb\u8f6c"), _T("\u64cd\u4f5c"), _T("\u65cb\u8f6c\u5931\u8d25\u65f6\u81ea\u52a8\u5c1d\u8bd5\u5de6\u53f3\u5404\u504f\u79fb 1 \u683c\u8fdb\u884c\u4fee\u6b63\u3002") }, - { UPGRADE_TIME_DILATION, 1, 80, false, _T("\u65f6\u95f4\u7f13\u6d41"), _T("\u4fdd\u547d"), _T("\u76d8\u9762\u63a5\u8fd1\u9876\u7aef\u65f6\u81ea\u52a8\u964d\u4f4e\u4e0b\u843d\u901f\u5ea6\uff0c\u4e3a\u8865\u6551\u7559\u51fa\u53cd\u5e94\u7a7a\u95f4\u3002") }, - { UPGRADE_HIGH_PRESSURE, 1, 70, false, _T("\u9ad8\u538b\u5956\u52b1"), _T("\u98ce\u9669"), _T("\u4e0b\u843d\u901f\u5ea6\u63d0\u9ad8 15%\uff0c\u4f46\u5f97\u5206\u4e0e EXP \u989d\u5916\u63d0\u9ad8 50%\u3002") }, - { UPGRADE_TETRIS_GAMBLE, 1, 78, false, _T("\u8d4c\u547d\u56db\u6d88"), _T("\u98ce\u9669"), _T("\u666e\u901a 1~3 \u6d88\u6536\u76ca\u964d\u4f4e\uff0c\u4f46 4 \u6d88\u6536\u76ca\u66b4\u589e\u3002") }, - { UPGRADE_EXTREME_PLAYER, 1, 114, false, _T("\u6781\u9650\u73a9\u5bb6"), _T("\u8fdb\u5316"), _T("\u7ec4\u5408\u9ad8\u538b\u4e0e\u8d4c\u547d\u56db\u6d88\uff0c\u901f\u5ea6\u8fdb\u4e00\u6b65\u63d0\u9ad8\uff0c\u56db\u6d88\u540e\u77ed\u6682\u7f13\u901f\u3002") }, - { UPGRADE_UPGRADE_SHOCKWAVE, 1, 106, false, _T("\u5347\u7ea7\u51b2\u51fb\u6ce2"), _T("\u8fdb\u9636"), _T("\u5373\u5c06\u5347\u7ea7\u65f6\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 2 \u884c\uff0c\u5f3a\u5316\u5347\u7ea7\u8282\u594f\u53cd\u9988\u3002") }, - { UPGRADE_EVOLUTION_IMPACT, 1, 118, false, _T("\u8fdb\u5316\u51b2\u51fb"), _T("\u8fdb\u5316"), _T("\u5347\u7ea7\u65f6\u6e05\u9664\u5e95\u90e8 3 \u884c\uff0c\u5e76\u83b7\u5f97 10 \u79d2\u53cc\u500d EXP\u3002") }, - { UPGRADE_CONTROL_MASTER, 1, 112, false, _T("\u64cd\u63a7\u5927\u5e08"), _T("\u8fdb\u5316"), _T("Hold \u540e\u77ed\u6682\u964d\u4f4e\u4e0b\u843d\u901f\u5ea6\uff0c\u5e76\u989d\u5916\u589e\u52a0 1 \u4e2a\u9884\u89c8\u65b9\u5757\u3002") }, - { UPGRADE_BLOCK_STORM, 1, 82, false, _T("\u65b9\u5757\u98ce\u66b4"), _T("\u723d\u611f"), _T("\u63a5\u4e0b\u6765 5 \u4e2a\u65b9\u5757\u5168\u90e8\u53d8\u6210 I \u5757\uff0c\u5feb\u901f\u5236\u9020\u56db\u6d88\u673a\u4f1a\u3002") }, - { 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") }, - { UPGRADE_GAMBLER, -1, 64, true, _T("\u8d4c\u5f92"), _T("\u7279\u6b8a"), _T("\u9009\u62e9\u5f3a\u5316\u65f6\uff0c\u6709\u6982\u7387\u53cc\u500d\u751f\u6548\uff0c\u4e5f\u6709\u6982\u7387\u5b8c\u5168\u843d\u7a7a\u3002") } -}; - -static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]); -static int GetTopOccupiedRow(); - int bricks[7][4][4][4] = { { @@ -234,7 +144,7 @@ static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxR * @param brickType 方块类型编号。 * @return Point 计算得到的生成坐标。 */ -static Point GetSpawnPoint(int brickType) +Point GetSpawnPoint(int brickType) { int minRow, maxRow, minCol, maxCol; GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol); @@ -248,7 +158,7 @@ static Point GetSpawnPoint(int brickType) return spawnPoint; } -static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) +void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) { stats.score = 0; stats.level = 1; @@ -319,484 +229,14 @@ static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) } } -static int GetNextPreviewLimit() -{ - if (currentMode != MODE_ROGUE) - { - return 1; - } - - if (rogueStats.previewCount < 1) - { - return 1; - } - - if (rogueStats.previewCount > 3) - { - return 3; - } - - return rogueStats.previewCount; -} - -static int GetUpgradeCurrentLevel(int upgradeId) -{ - switch (upgradeId) - { - case UPGRADE_SCORE_MULTIPLIER: - return rogueStats.scoreUpgradeLevel; - case UPGRADE_EXP_MULTIPLIER: - return rogueStats.expUpgradeLevel; - case UPGRADE_SLOW_FALL: - return rogueStats.slowFallStacks; - case UPGRADE_COMBO_BONUS: - return rogueStats.comboBonusStacks; - case UPGRADE_PREVIEW_PLUS_ONE: - return rogueStats.previewUpgradeLevel; - case UPGRADE_LAST_CHANCE: - return rogueStats.lastChanceUpgradeLevel; - case UPGRADE_HOLD_UNLOCK: - return rogueStats.holdUnlocked; - case UPGRADE_PRESSURE_RELIEF: - return rogueStats.pressureReliefLevel; - case UPGRADE_SWEEPER: - return rogueStats.sweeperLevel; - case UPGRADE_EXPLOSIVE_PIECE: - return rogueStats.explosiveLevel; - case UPGRADE_CHAIN_BLAST: - return rogueStats.chainBlastLevel; - case UPGRADE_CHAIN_BOMB: - return rogueStats.chainBombLevel; - case UPGRADE_LASER_PIECE: - return rogueStats.laserLevel; - case UPGRADE_THUNDER_TETRIS: - return rogueStats.thunderTetrisLevel; - case UPGRADE_THUNDER_LASER: - return rogueStats.thunderLaserLevel; - case UPGRADE_FEVER_MODE: - return rogueStats.feverLevel; - case UPGRADE_RAGE_STACK: - return rogueStats.rageStackLevel; - case UPGRADE_INFINITE_FEVER: - return rogueStats.infiniteFeverLevel; - case UPGRADE_SCREEN_BOMB: - return rogueStats.screenBombLevel; - case UPGRADE_TERMINAL_CLEAR: - return rogueStats.terminalClearLevel; - case UPGRADE_DUAL_CHOICE: - return rogueStats.dualChoiceLevel; - case UPGRADE_DESTINY_WHEEL: - return rogueStats.destinyWheelLevel; - case UPGRADE_PERFECT_ROTATE: - return rogueStats.perfectRotateLevel; - case UPGRADE_TIME_DILATION: - return rogueStats.timeDilationLevel; - case UPGRADE_HIGH_PRESSURE: - return rogueStats.highPressureLevel; - case UPGRADE_TETRIS_GAMBLE: - return rogueStats.tetrisGambleLevel; - case UPGRADE_EXTREME_PLAYER: - return rogueStats.extremePlayerLevel; - case UPGRADE_UPGRADE_SHOCKWAVE: - return rogueStats.upgradeShockwaveLevel; - case UPGRADE_EVOLUTION_IMPACT: - return rogueStats.evolutionImpactLevel; - case UPGRADE_CONTROL_MASTER: - return rogueStats.controlMasterLevel; - case UPGRADE_BLOCK_STORM: - return rogueStats.blockStormLevel; - case UPGRADE_CROSS_PIECE: - return rogueStats.crossPieceLevel; - case UPGRADE_BLACK_HOLE: - 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: - return rogueStats.doubleGrowthLevel; - case UPGRADE_GAMBLER: - return rogueStats.gamblerLevel; - default: - return 0; - } -} - -static int GetUpgradeDynamicWeight(const UpgradeEntry& entry) -{ - int weight = entry.baseWeight; - int currentLevel = GetUpgradeCurrentLevel(entry.id); - int topOccupiedRow = GetTopOccupiedRow(); - bool boardIsDangerous = (topOccupiedRow >= 0 && topOccupiedRow <= 6); - bool boardIsStable = (topOccupiedRow < 0 || topOccupiedRow >= 12); - - if (entry.repeatable) - { - weight -= currentLevel * 8; - } - else if (currentLevel > 0) - { - weight = 0; - } - - switch (entry.id) - { - case UPGRADE_SCORE_MULTIPLIER: - if (rogueStats.level >= 4) - { - weight += 8; - } - break; - case UPGRADE_EXP_MULTIPLIER: - if (rogueStats.level <= 5) - { - weight += 16; - } - break; - case UPGRADE_SLOW_FALL: - if (boardIsDangerous) - { - weight += 22; - } - break; - case UPGRADE_COMBO_BONUS: - if (rogueStats.comboChain >= 2) - { - weight += 18; - } - break; - case UPGRADE_PREVIEW_PLUS_ONE: - if (boardIsStable) - { - weight += 10; - } - break; - case UPGRADE_LAST_CHANCE: - if (boardIsDangerous) - { - weight += 30; - } - break; - case UPGRADE_HOLD_UNLOCK: - if (rogueStats.holdUnlocked == 0 && rogueStats.level <= 6) - { - weight += 14; - } - break; - case UPGRADE_PRESSURE_RELIEF: - if (boardIsDangerous) - { - weight += 26; - } - break; - case UPGRADE_SWEEPER: - if (rogueStats.totalLinesCleared >= 8) - { - weight += 12; - } - break; - case UPGRADE_EXPLOSIVE_PIECE: - if (rogueStats.explosiveLevel == 0) - { - weight += 12; - } - break; - case UPGRADE_CHAIN_BLAST: - weight += rogueStats.explosiveLevel * 20; - break; - case UPGRADE_CHAIN_BOMB: - weight += 28; - break; - case UPGRADE_LASER_PIECE: - if (rogueStats.laserLevel == 0) - { - weight += 10; - } - break; - case UPGRADE_THUNDER_TETRIS: - weight += rogueStats.laserLevel * 18; - break; - case UPGRADE_THUNDER_LASER: - weight += 28; - break; - case UPGRADE_FEVER_MODE: - if (rogueStats.totalLinesCleared >= 10) - { - weight += 14; - } - break; - case UPGRADE_RAGE_STACK: - if (rogueStats.comboBonusStacks > 0 || rogueStats.comboChain >= 2) - { - weight += 18; - } - break; - case UPGRADE_INFINITE_FEVER: - if (rogueStats.feverLevel > 0 && rogueStats.rageStackLevel > 0) - { - weight += 34; - } - break; - case UPGRADE_SCREEN_BOMB: - if (boardIsDangerous) - { - weight += 18; - } - break; - case UPGRADE_TERMINAL_CLEAR: - weight += 34; - break; - case UPGRADE_DUAL_CHOICE: - if (rogueStats.level <= 6) - { - weight += 10; - } - break; - case UPGRADE_DESTINY_WHEEL: - weight += 30; - break; - case UPGRADE_PERFECT_ROTATE: - if (rogueStats.holdUnlocked == 0 || boardIsDangerous) - { - weight += 14; - } - break; - case UPGRADE_TIME_DILATION: - if (boardIsDangerous) - { - weight += 24; - } - break; - case UPGRADE_HIGH_PRESSURE: - if (boardIsStable && rogueStats.level >= 3) - { - weight += 18; - } - break; - case UPGRADE_TETRIS_GAMBLE: - if (rogueStats.level >= 4) - { - weight += 14; - } - break; - case UPGRADE_EXTREME_PLAYER: - weight += 36; - break; - case UPGRADE_UPGRADE_SHOCKWAVE: - weight += 34; - break; - case UPGRADE_EVOLUTION_IMPACT: - weight += 40; - break; - case UPGRADE_CONTROL_MASTER: - weight += 34; - break; - case UPGRADE_BLOCK_STORM: - if (rogueStats.tetrisGambleLevel > 0 || rogueStats.thunderTetrisLevel > 0) - { - weight += 20; - } - break; - case UPGRADE_CROSS_PIECE: - if (rogueStats.laserLevel > 0 || rogueStats.feverLevel > 0) - { - weight += 14; - } - break; - case UPGRADE_BLACK_HOLE: - if (boardIsDangerous) - { - weight += 18; - } - break; - case UPGRADE_AIR_RESHAPE: - if (boardIsDangerous || rogueStats.tetrisGambleLevel > 0) - { - 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) - { - weight += 14; - } - break; - case UPGRADE_DOUBLE_GROWTH: - if (rogueStats.level >= 3) - { - weight += 12; - } - break; - case UPGRADE_PIECE_TUNING: - if (rogueStats.level >= 4) - { - weight += 8; - } - break; - case UPGRADE_GAMBLER: - if (rogueStats.level >= 5) - { - weight += 10; - } - break; - default: - break; - } - - if (weight < 1) - { - weight = 1; - } - - return weight; -} - -static const TCHAR* GetPieceShortName(int pieceType) -{ - static const TCHAR* kPieceNames[7] = - { - _T("I"), - _T("T"), - _T("L"), - _T("J"), - _T("O"), - _T("S"), - _T("Z") - }; - - if (pieceType < 0 || pieceType >= 7) - { - return _T("?"); - } - - return kPieceNames[pieceType]; -} - -static bool IsUpgradeSelectable(const UpgradeEntry& entry) -{ - if (entry.id == UPGRADE_CHAIN_BLAST) - { - return rogueStats.explosiveLevel > 0 && rogueStats.chainBlastLevel == 0; - } - - if (entry.id == UPGRADE_CHAIN_BOMB) - { - return rogueStats.explosiveLevel > 0 && rogueStats.chainBlastLevel > 0 && rogueStats.chainBombLevel == 0; - } - - if (entry.id == UPGRADE_THUNDER_TETRIS) - { - return rogueStats.laserLevel > 0 && rogueStats.thunderTetrisLevel == 0; - } - - if (entry.id == UPGRADE_THUNDER_LASER) - { - return rogueStats.laserLevel > 0 && rogueStats.thunderTetrisLevel > 0 && rogueStats.thunderLaserLevel == 0; - } - - if (entry.id == UPGRADE_FEVER_MODE) - { - return rogueStats.feverLevel == 0; - } - - if (entry.id == UPGRADE_RAGE_STACK) - { - return rogueStats.rageStackLevel == 0; - } - - if (entry.id == UPGRADE_INFINITE_FEVER) - { - return rogueStats.feverLevel > 0 && rogueStats.rageStackLevel > 0 && rogueStats.infiniteFeverLevel == 0; - } - - if (entry.id == UPGRADE_SCREEN_BOMB) - { - return rogueStats.screenBombLevel == 0; - } - - if (entry.id == UPGRADE_TERMINAL_CLEAR) - { - return rogueStats.screenBombLevel > 0 && rogueStats.lastChanceUpgradeLevel > 0 && rogueStats.terminalClearLevel == 0; - } - - if (entry.id == UPGRADE_DUAL_CHOICE) - { - return rogueStats.dualChoiceLevel == 0; - } - - if (entry.id == UPGRADE_DESTINY_WHEEL) - { - return rogueStats.gamblerLevel > 0 && rogueStats.dualChoiceLevel > 0 && rogueStats.destinyWheelLevel == 0; - } - - if (entry.id == UPGRADE_EXTREME_PLAYER) - { - return rogueStats.highPressureLevel > 0 && rogueStats.tetrisGambleLevel > 0 && rogueStats.extremePlayerLevel == 0; - } - - if (entry.id == UPGRADE_UPGRADE_SHOCKWAVE) - { - return rogueStats.scoreUpgradeLevel > 0 && - rogueStats.expUpgradeLevel > 0 && - rogueStats.pressureReliefLevel > 0 && - rogueStats.upgradeShockwaveLevel == 0; - } - - if (entry.id == UPGRADE_EVOLUTION_IMPACT) - { - return rogueStats.upgradeShockwaveLevel > 0 && - rogueStats.doubleGrowthLevel > 0 && - rogueStats.evolutionImpactLevel == 0; - } - - if (entry.id == UPGRADE_CONTROL_MASTER) - { - return rogueStats.perfectRotateLevel > 0 && - rogueStats.holdUnlocked > 0 && - rogueStats.controlMasterLevel == 0; - } - - if (entry.id == UPGRADE_VOID_CORE) - { - return rogueStats.blackHoleLevel > 0 && - rogueStats.rainbowPieceLevel > 0 && - rogueStats.voidCoreLevel == 0; - } - - if (entry.repeatable) - { - return true; - } - - if (entry.maxLevel <= 0) - { - return GetUpgradeCurrentLevel(entry.id) == 0; - } - - return GetUpgradeCurrentLevel(entry.id) < entry.maxLevel; -} - -static void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks) +void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks) { feedbackState.visibleTicks = ticks; lstrcpyn(feedbackState.title, title, sizeof(feedbackState.title) / sizeof(TCHAR)); lstrcpyn(feedbackState.detail, detail, sizeof(feedbackState.detail) / sizeof(TCHAR)); } -static bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) +bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) { for (int i = 0; i < 4; i++) { @@ -825,1238 +265,6 @@ static bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) return true; } -static int GetTopOccupiedRow() -{ - for (int i = 0; i < nGameHeight; i++) - { - for (int j = 0; j < nGameWidth; j++) - { - if (workRegion[i][j] != 0) - { - return i; - } - } - } - - return -1; -} - -static int GetSweeperThreshold() -{ - int reduction = (rogueStats.sweeperLevel - 1) * 2; - int threshold = 8 - reduction; - return threshold < 3 ? 3 : threshold; -} - -static bool RollExplosivePiece() -{ - if (currentMode != MODE_ROGUE || rogueStats.explosiveLevel <= 0) - { - return false; - } - - if (rogueStats.explosivePieceCounter < 10) - { - rogueStats.explosivePieceCounter++; - } - - if (rogueStats.explosivePieceCounter < 10) - { - return false; - } - - rogueStats.explosivePieceCounter = 0; - return true; -} - -static bool RollLaserPiece() -{ - if (currentMode != MODE_ROGUE || rogueStats.laserLevel <= 0) - { - return false; - } - - int chancePercent = 10 + (rogueStats.laserLevel - 1) * 8; - if (chancePercent > 35) - { - chancePercent = 35; - } - - if (rogueStats.feverTicks > 0) - { - chancePercent += 10; - if (chancePercent > 50) - { - chancePercent = 50; - } - } - - return (rand() % 100) < chancePercent; -} - -static bool RollCrossPiece() -{ - if (currentMode != MODE_ROGUE || rogueStats.crossPieceLevel <= 0) - { - return false; - } - - int chancePercent = 8 + (rogueStats.crossPieceLevel - 1) * 6; - if (chancePercent > 28) - { - chancePercent = 28; - } - - if (rogueStats.feverTicks > 0) - { - chancePercent += 10; - if (chancePercent > 40) - { - chancePercent = 40; - } - } - - return (rand() % 100) < chancePercent; -} - -static bool RollRainbowPiece() -{ - if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0) - { - return false; - } - - int chancePercent = 12; - if (rogueStats.feverTicks > 0) - { - chancePercent += 10; - } - 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) - { - 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) -{ - int radius = (currentMode == MODE_ROGUE && rogueStats.chainBombLevel > 0) ? 2 : 1; - int clearedCellCount = 0; - - for (int y = centerY - radius; y <= centerY + radius; y++) - { - for (int x = centerX - radius; x <= centerX + radius; x++) - { - if (y >= 0 && y < nGameHeight && x >= 0 && x < nGameWidth) - { - if (workRegion[y][x] != 0) - { - clearedCellCount++; - workRegion[y][x] = 0; - } - } - } - } - - return clearedCellCount; -} - -static int ClearColumnAt(int column) -{ - int clearedCellCount = 0; - - if (column < 0 || column >= nGameWidth) - { - return 0; - } - - for (int y = 0; y < nGameHeight; y++) - { - if (workRegion[y][column] != 0) - { - workRegion[y][column] = 0; - clearedCellCount++; - } - } - - return clearedCellCount; -} - -static int ClearRowAt(int row) -{ - int clearedCellCount = 0; - - if (row < 0 || row >= nGameHeight) - { - return 0; - } - - for (int x = 0; x < nGameWidth; x++) - { - if (workRegion[row][x] != 0) - { - workRegion[row][x] = 0; - clearedCellCount++; - } - } - - 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 }; - 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) - { - return 0; - } - - int clearedCellCount = 0; - for (int y = 0; y < nGameHeight; y++) - { - for (int x = 0; x < nGameWidth; x++) - { - if (workRegion[y][x] == targetBlock) - { - workRegion[y][x] = 0; - clearedCellCount++; - } - } - } - - return clearedCellCount; -} - -static int TriggerScreenBomb() -{ - int clearedCellCount = 0; - - for (int i = 0; i < 5; i++) - { - int row = nGameHeight - 1 - i; - if (row < 0) - { - break; - } - - for (int x = 0; x < nGameWidth; x++) - { - if (workRegion[row][x] != 0) - { - clearedCellCount++; - } - } - - DeleteOneLine(row); - } - - return clearedCellCount; -} - -static int TriggerChainBlast(int lineAnchor) -{ - if (currentMode != MODE_ROGUE || rogueStats.chainBlastLevel <= 0) - { - return 0; - } - - int clearedCellCount = 0; - int blastCount = 2 + rogueStats.chainBlastLevel; - - for (int blastIndex = 0; blastIndex < blastCount; blastIndex++) - { - int randomY = lineAnchor + (rand() % 3) - 1; - int randomX = rand() % nGameWidth; - - if (randomY < 0) - { - randomY = 0; - } - if (randomY >= nGameHeight) - { - randomY = nGameHeight - 1; - } - - if (workRegion[randomY][randomX] != 0) - { - workRegion[randomY][randomX] = 0; - clearedCellCount++; - } - } - - return clearedCellCount; -} - -static int RollNextPieceType() -{ - if (currentMode == MODE_ROGUE && rogueStats.blockStormPiecesRemaining > 0) - { - rogueStats.blockStormPiecesRemaining--; - return 0; - } - - int weights[7] = { 100, 100, 100, 100, 100, 100, 100 }; - - if (currentMode == MODE_ROGUE) - { - for (int i = 0; i < 7; i++) - { - weights[i] -= rogueStats.pieceTuningLevels[i] * 20; - if (weights[i] < 20) - { - weights[i] = 20; - } - } - } - - int totalWeight = 0; - for (int i = 0; i < 7; i++) - { - totalWeight += weights[i]; - } - - int roll = rand() % totalWeight; - for (int i = 0; i < 7; i++) - { - if (roll < weights[i]) - { - return i; - } - roll -= weights[i]; - } - - return rand() % 7; -} - -static int TryStabilizeBoard() -{ - if (currentMode != MODE_ROGUE || rogueStats.stableStructureLevel <= 0) - { - return 0; - } - - int triggerChance = 15 + (rogueStats.stableStructureLevel - 1) * 10; - if (triggerChance > 50) - { - triggerChance = 50; - } - - if ((rand() % 100) >= triggerChance) - { - return 0; - } - - for (int y = nGameHeight - 2; y >= 1; y--) - { - for (int x = 1; x < nGameWidth - 1; x++) - { - if (workRegion[y][x] != 0) - { - continue; - } - - bool hasSupportBelow = workRegion[y + 1][x] != 0; - bool hasLeftNeighbor = workRegion[y][x - 1] != 0; - bool hasRightNeighbor = workRegion[y][x + 1] != 0; - - if (hasSupportBelow && hasLeftNeighbor && hasRightNeighbor) - { - int fillValue = workRegion[y + 1][x]; - if (fillValue == 0) - { - fillValue = hasLeftNeighbor ? workRegion[y][x - 1] : workRegion[y][x + 1]; - } - workRegion[y][x] = fillValue; - return 1; - } - } - } - - return 0; -} - -static void ResetNextQueue() -{ - for (int i = 0; i < 3; i++) - { - nextTypes[i] = RollNextPieceType(); - } -} - -static int ConsumeNextType() -{ - int nextType = nextTypes[0]; - nextTypes[0] = nextTypes[1]; - nextTypes[1] = nextTypes[2]; - nextTypes[2] = RollNextPieceType(); - return nextType; -} - -static int GetRogueScoreByLines(int linesCleared) -{ - switch (linesCleared) - { - case 1: return 100; - case 2: return 300; - case 3: return 500; - case 4: return 800; - default: return 0; - } -} - -static int GetRogueExpByLines(int linesCleared) -{ - switch (linesCleared) - { - case 1: return 10; - case 2: return 25; - case 3: return 45; - case 4: return 80; - default: return 0; - } -} - -static int ApplyLevelProgress(PlayerStats& stats) -{ - int levelUps = 0; - - while (stats.requiredExp > 0 && stats.exp >= stats.requiredExp) - { - stats.exp -= stats.requiredExp; - stats.level++; - stats.requiredExp = 20 + stats.level * 10; - levelUps++; - } - - return levelUps; -} - -static int TriggerUpgradeShockwave(int rowsToClear) -{ - int clearedRows = 0; - - for (int i = 0; i < rowsToClear; i++) - { - DeleteOneLine(nGameHeight - 1); - clearedRows++; - } - - return clearedRows; -} - -static void FillUpgradeOptions() -{ - int selectableIndexes[kUpgradePoolSize] = { 0 }; - int selectableWeights[kUpgradePoolSize] = { 0 }; - int selectableCount = 0; - - for (int i = 0; i < kUpgradePoolSize; i++) - { - if (IsUpgradeSelectable(kUpgradePool[i])) - { - selectableIndexes[selectableCount] = i; - selectableWeights[selectableCount] = GetUpgradeDynamicWeight(kUpgradePool[i]); - selectableCount++; - } - } - - int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 5 : 3; - int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit; - upgradeUiState.optionCount = optionCount; - upgradeUiState.selectedIndex = 0; - upgradeUiState.picksRemaining = (rogueStats.dualChoiceLevel > 0 || rogueStats.destinyWheelLevel > 0) ? 2 : 1; - - 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 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].cursed = false; - upgradeUiState.options[i].name = pickedEntry.name; - upgradeUiState.options[i].category = pickedEntry.category; - upgradeUiState.options[i].description = pickedEntry.description; - - if (pickedEntry.id == UPGRADE_PIECE_TUNING) - { - int targetPieceType = rand() % 7; - bool duplicatedPiece = true; - int guard = 0; - - while (duplicatedPiece && guard < 16) - { - duplicatedPiece = false; - for (int optionIndex = 0; optionIndex < i; optionIndex++) - { - if (upgradeUiState.options[optionIndex].id == UPGRADE_PIECE_TUNING && - upgradeUiState.options[optionIndex].targetPieceType == targetPieceType) - { - duplicatedPiece = true; - targetPieceType = (targetPieceType + 1) % 7; - break; - } - } - guard++; - } - - upgradeUiState.options[i].targetPieceType = targetPieceType; - upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[targetPieceType]; - upgradeUiState.options[i].name = _T("\u65b9\u5757\u6539\u9020"); - - static TCHAR tuningDescriptions[4][64]; - _stprintf_s( - tuningDescriptions[i], - _T("\u964d\u4f4e %s \u65b9\u5757\u7684\u51fa\u73b0\u6982\u7387\u3002"), - GetPieceShortName(targetPieceType)); - upgradeUiState.options[i].description = tuningDescriptions[i]; - } - - 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; - } -} - -int GetRogueFallInterval() -{ - int baseInterval = 500 + rogueStats.slowFallStacks * 80; - - if (rogueStats.highPressureLevel > 0) - { - baseInterval = baseInterval * 85 / 100; - } - - if (rogueStats.extremePlayerLevel > 0) - { - baseInterval = baseInterval * 90 / 100; - } - - if (rogueStats.extremeDangerLevel > 0) - { - baseInterval = baseInterval * (100 - rogueStats.extremeDangerLevel * 8) / 100; - } - - if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0) - { - baseInterval += 120; - } - - if (rogueStats.timeDilationTicks > 0) - { - baseInterval = baseInterval * 130 / 100; - } - - if (rogueStats.extremeSlowTicks > 0) - { - baseInterval += 180; - } - - if (rogueStats.holdSlowTicks > 0) - { - baseInterval += 140; - } - - if (baseInterval < 120) - { - baseInterval = 120; - } - - return baseInterval; -} - -static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) -{ - if (applyCount <= 0) - { - 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; - currentFallInterval = GetRogueFallInterval(); - break; - case UPGRADE_PREVIEW_PLUS_ONE: - for (int i = 0; i < applyCount; i++) - { - if (rogueStats.previewCount < 3) - { - rogueStats.previewCount++; - } - } - rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; - break; - case UPGRADE_LAST_CHANCE: - rogueStats.lastChanceCount += applyCount; - rogueStats.lastChanceUpgradeLevel += applyCount; - break; - case UPGRADE_HOLD_UNLOCK: - rogueStats.holdUnlocked = 1; - 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; - break; - case UPGRADE_EXPLOSIVE_PIECE: - rogueStats.explosiveLevel += applyCount; - break; - case UPGRADE_CHAIN_BLAST: - rogueStats.chainBlastLevel = 1; - break; - case UPGRADE_CHAIN_BOMB: - rogueStats.chainBombLevel = 1; - break; - case UPGRADE_LASER_PIECE: - rogueStats.laserLevel += applyCount; - 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 = 1; - 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: - rogueStats.blockStormLevel = 1; - rogueStats.blockStormPiecesRemaining = 2; - nextTypes[0] = 0; - nextTypes[1] = 0; - nextTypes[2] = 0; - break; - case UPGRADE_CROSS_PIECE: - rogueStats.crossPieceLevel += applyCount; - break; - case UPGRADE_BLACK_HOLE: - rogueStats.blackHoleLevel = 1; - rogueStats.blackHoleCharges++; - break; - case UPGRADE_AIR_RESHAPE: - 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; - case UPGRADE_DOUBLE_GROWTH: - rogueStats.doubleGrowthLevel = 1; - rogueStats.scoreMultiplierPercent += 15; - rogueStats.expMultiplierPercent += 15; - break; - case UPGRADE_PIECE_TUNING: - if (targetPieceType >= 0 && targetPieceType < 7) - { - rogueStats.pieceTuningLevels[targetPieceType] += applyCount; - } - break; - case UPGRADE_GAMBLER: - rogueStats.gamblerLevel += applyCount; - break; - default: - break; - } -} - -static void ApplyLineClearResult(int linesCleared) -{ - if (linesCleared <= 0) - { - if (currentMode == MODE_ROGUE) - { - rogueStats.comboChain = 0; - } - return; - } - - if (currentMode == MODE_CLASSIC) - { - classicStats.totalLinesCleared += linesCleared; - classicStats.score += linesCleared * 100; - tScore = classicStats.score; - return; - } - - int scoreGain = GetRogueScoreByLines(linesCleared); - scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100; - - int expGain = GetRogueExpByLines(linesCleared); - expGain = expGain * rogueStats.expMultiplierPercent / 100; - - if (rogueStats.tetrisGambleLevel > 0) - { - if (linesCleared == 4) - { - scoreGain *= 4; - } - else - { - scoreGain = scoreGain * 50 / 100; - } - } - - if (rogueStats.doubleGrowthLevel > 0) - { - int growthMultiplierPercent = 100 + rogueStats.doubleGrowthLevel * 15; - scoreGain = scoreGain * growthMultiplierPercent / 100; - expGain = expGain * growthMultiplierPercent / 100; - } - - if (rogueStats.feverTicks > 0) - { - scoreGain *= 2; - expGain *= 2; - } - - int gamblerBonusPercent = 0; - if (rogueStats.gamblerLevel > 0) - { - int variance = 20 + (rogueStats.gamblerLevel - 1) * 10; - if (variance > 50) - { - variance = 50; - } - - gamblerBonusPercent = (rand() % (variance * 2 + 1)) - variance; - scoreGain = scoreGain * (100 + gamblerBonusPercent) / 100; - expGain = expGain * (100 + gamblerBonusPercent) / 100; - } - - rogueStats.comboChain++; - if (rogueStats.comboBonusStacks > 0 && rogueStats.comboChain > 1) - { - scoreGain += (rogueStats.comboChain - 1) * rogueStats.comboBonusStacks * 50; - } - - if (rogueStats.rageStackLevel > 0 && rogueStats.comboChain > 1) - { - int rageBonusPercent = (rogueStats.comboChain - 1) * 15; - if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0) - { - rageBonusPercent += (rogueStats.comboChain - 1) * 10; - } - scoreGain += scoreGain * rageBonusPercent / 100; - } - - rogueStats.totalLinesCleared += linesCleared; - rogueStats.score += scoreGain; - rogueStats.exp += expGain; - - if (rogueStats.feverLevel > 0) - { - rogueStats.feverLineCharge += linesCleared; - while (rogueStats.feverLineCharge >= 20) - { - rogueStats.feverLineCharge -= 20; - rogueStats.feverTicks = 10; - currentFallInterval = GetRogueFallInterval(); - SetFeedbackMessage(_T("\u8fdb\u5165\u72c2\u70ed\u6a21\u5f0f"), _T("\u672a\u6765 10 \u79d2\u5f97\u5206 / EXP x2\uff0c\u4e0b\u843d\u66f4\u6162\uff0c\u7279\u6b8a\u65b9\u5757\u66f4\u6613\u51fa\u73b0\u3002"), 12); - } - } - - if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0) - { - rogueStats.feverTicks += 1; - if (linesCleared == 4) - { - rogueStats.feverTicks += 3; - } - if (rogueStats.feverTicks > 20) - { - rogueStats.feverTicks = 20; - } - currentFallInterval = GetRogueFallInterval(); - } - - if (rogueStats.extremePlayerLevel > 0 && linesCleared == 4) - { - rogueStats.extremeSlowTicks = 5; - rogueStats.extremeDangerTicks = 30; - currentFallInterval = GetRogueFallInterval(); - SetFeedbackMessage(_T("\u6781\u9650\u73a9\u5bb6\u89e6\u53d1"), _T("\u56db\u6d88\u6210\u529f\uff0c\u63a5\u4e0b\u6765 5 \u79d2\u77ed\u6682\u7f13\u901f\u8865\u4f4d\u3002"), 12); - } - - if (rogueStats.screenBombLevel > 0) - { - rogueStats.screenBombCharge += linesCleared; - while (rogueStats.screenBombCharge >= 30) - { - rogueStats.screenBombCharge -= 30; - rogueStats.screenBombCount++; - SetFeedbackMessage(_T("\u6e05\u5c4f\u70b8\u5f39\u5c31\u7eea"), _T("\u5df2\u83b7\u5f97 1 \u6b21\u6e05\u9664\u5e95\u90e8 5 \u884c\u7684\u673a\u4f1a\u3002"), 12); - } - } - - if (rogueStats.chainBlastLevel > 0) - { - int chainBlastCells = 0; - for (int i = 0; i < linesCleared; i++) - { - chainBlastCells += TriggerChainBlast(nGameHeight - 1 - i); - } - - if (chainBlastCells > 0) - { - int chainBlastScore = chainBlastCells * rogueStats.scoreMultiplierPercent / 100; - if (chainBlastScore < chainBlastCells) - { - chainBlastScore = chainBlastCells; - } - rogueStats.score += chainBlastScore; - tScore = rogueStats.score; - - TCHAR blastDetail[128]; - _stprintf_s( - blastDetail, - _T("\u989d\u5916\u6e05\u9664 %d \u683c +%d Score"), - chainBlastCells, - chainBlastScore); - SetFeedbackMessage(_T("\u8fde\u9501\u7206\u7834\u89e6\u53d1"), blastDetail, 12); - } - } - - if (linesCleared == 4 && rogueStats.thunderTetrisLevel > 0) - { - int thunderRowsCleared = 0; - for (int i = 0; i < 2; i++) - { - int randomRow = rand() % nGameHeight; - for (int x = 0; x < nGameWidth; x++) - { - if (workRegion[randomRow][x] != 0) - { - workRegion[randomRow][x] = 0; - thunderRowsCleared++; - } - } - } - - if (thunderRowsCleared > 0) - { - int thunderScore = thunderRowsCleared * rogueStats.scoreMultiplierPercent / 100; - if (thunderScore < thunderRowsCleared) - { - thunderScore = thunderRowsCleared; - } - rogueStats.score += thunderScore; - tScore = rogueStats.score; - SetFeedbackMessage(_T("\u96f7\u9706\u56db\u6d88\u89e6\u53d1"), _T("\u56db\u6d88\u540e\u989d\u5916\u6e05\u7406\u4e86 2 \u884c\u8303\u56f4\u5185\u7684\u683c\u5b50\u3002"), 12); - } - } - - if (linesCleared == 4 && rogueStats.thunderLaserLevel > 0) - { - int laserCellsCleared = 0; - for (int i = 0; i < 2; i++) - { - laserCellsCleared += ClearColumnAt(rand() % nGameWidth); - } - - if (laserCellsCleared > 0) - { - int laserScore = laserCellsCleared * rogueStats.scoreMultiplierPercent / 100; - int laserExp = laserCellsCleared * rogueStats.expMultiplierPercent / 100; - if (laserScore < laserCellsCleared) - { - laserScore = laserCellsCleared; - } - if (laserExp < laserCellsCleared) - { - laserExp = laserCellsCleared; - } - - rogueStats.score += laserScore; - rogueStats.exp += laserExp; - tScore = rogueStats.score; - - TCHAR thunderLaserDetail[128]; - _stprintf_s( - thunderLaserDetail, - _T("\u968f\u673a\u6fc0\u5149\u6e05\u9664 %d \u683c +%d Score +%d EXP"), - laserCellsCleared, - laserScore, - laserExp); - SetFeedbackMessage(_T("\u96f7\u9706\u6fc0\u5149\u89e6\u53d1"), thunderLaserDetail, 12); - } - } - - if (rogueStats.sweeperLevel > 0) - { - rogueStats.sweeperCharge += linesCleared; - int sweeperThreshold = GetSweeperThreshold(); - int clearedBySweeper = 0; - - while (rogueStats.sweeperCharge >= sweeperThreshold) - { - rogueStats.sweeperCharge -= sweeperThreshold; - DeleteOneLine(nGameHeight - 1); - clearedBySweeper++; - } - - if (clearedBySweeper > 0) - { - TCHAR sweeperDetail[128]; - _stprintf_s( - sweeperDetail, - _T("\u81ea\u52a8\u6e05\u7406\u5e95\u90e8 %d \u884c \u5269\u4f59\u5145\u80fd %d/%d"), - clearedBySweeper, - rogueStats.sweeperCharge, - sweeperThreshold); - SetFeedbackMessage(_T("\u6e05\u626b\u8005\u89e6\u53d1"), sweeperDetail, 12); - } - } - - int levelUps = ApplyLevelProgress(rogueStats); - upgradeUiState.pendingCount += levelUps; - tScore = rogueStats.score; - - if (levelUps > 0) - { - int shockwaveRows = 0; - if (rogueStats.evolutionImpactLevel > 0) - { - shockwaveRows = 3; - rogueStats.feverTicks = 10; - currentFallInterval = GetRogueFallInterval(); - } - else if (rogueStats.upgradeShockwaveLevel > 0) - { - shockwaveRows = 2; - } - - if (shockwaveRows > 0) - { - int clearedRows = TriggerUpgradeShockwave(shockwaveRows); - TCHAR shockwaveDetail[128]; - - if (rogueStats.evolutionImpactLevel > 0) - { - _stprintf_s( - shockwaveDetail, - _T("\u5347\u7ea7\u6e05\u9664\u5e95\u90e8 %d \u884c\uff0c\u5e76\u83b7\u5f97 10 \u79d2\u53cc\u500d EXP\u3002"), - clearedRows); - SetFeedbackMessage(_T("\u8fdb\u5316\u51b2\u51fb\u89e6\u53d1"), shockwaveDetail, 14); - } - else - { - _stprintf_s( - shockwaveDetail, - _T("\u5347\u7ea7\u6e05\u9664\u5e95\u90e8 %d \u884c\uff0c\u89e6\u53d1\u5347\u7ea7\u51b2\u51fb\u6ce2\u3002"), - clearedRows); - SetFeedbackMessage(_T("\u5347\u7ea7\u51b2\u51fb\u6ce2\u89e6\u53d1"), shockwaveDetail, 12); - } - } - } - - TCHAR feedbackTitle[64]; - TCHAR feedbackDetail[128]; - if (feedbackState.visibleTicks <= 0) - { - _stprintf_s(feedbackTitle, _T("+%d Score +%d EXP"), scoreGain, expGain); - if (levelUps > 0) - { - _stprintf_s(feedbackDetail, _T("Level Up x%d \u5f53\u524d Lv.%d"), levelUps, rogueStats.level); - } - else if (rogueStats.sweeperLevel > 0) - { - _stprintf_s( - feedbackDetail, - _T("\u6d88\u884c %d \u6e05\u626b\u8005 %d/%d"), - linesCleared, - rogueStats.sweeperCharge, - GetSweeperThreshold()); - } - else if (rogueStats.gamblerLevel > 0) - { - _stprintf_s(feedbackDetail, _T("\u6d88\u884c %d \u8d4c\u5f92\u6ce2\u52a8 %+d%%"), linesCleared, gamblerBonusPercent); - } - else - { - _stprintf_s(feedbackDetail, _T("\u6d88\u884c %d \u8fde\u51fb %d"), linesCleared, rogueStats.comboChain); - } - SetFeedbackMessage(feedbackTitle, feedbackDetail, 10); - } - - if (upgradeUiState.pendingCount > 0) - { - OpenUpgradeMenu(); - } - - currentFallInterval = GetRogueFallInterval(); -} - -static void ApplyDestinyCurse() -{ - int expPenalty = rogueStats.requiredExp * 25 / 100; - if (expPenalty < 10) - { - expPenalty = 10; - } - - rogueStats.requiredExp += expPenalty; -} - /** * @brief 判断当前方块是否可以继续向下移动。 * @@ -2732,303 +940,3 @@ void OpenRulesScreen() currentScreen = SCREEN_RULES; suspendFlag = false; } - -void OpenUpgradeMenu() -{ - if (currentMode != MODE_ROGUE || upgradeUiState.pendingCount <= 0) - { - return; - } - - FillUpgradeOptions(); - currentScreen = SCREEN_UPGRADE; -} - -void ConfirmUpgradeSelection() -{ - if (currentScreen != SCREEN_UPGRADE || upgradeUiState.optionCount <= 0) - { - return; - } - - UpgradeOption selectedOption = upgradeUiState.options[upgradeUiState.selectedIndex]; - int applyCount = 1; - TCHAR gamblerSuffix[64] = _T(""); - - if (currentMode == MODE_ROGUE && rogueStats.gamblerLevel > 0) - { - int gamblerChance = 20 + (rogueStats.gamblerLevel - 1) * 5; - if (gamblerChance > 40) - { - gamblerChance = 40; - } - - int roll = rand() % 100; - if (roll < gamblerChance) - { - applyCount = 2; - _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u53cc\u500d")); - } - else if (roll >= 100 - gamblerChance) - { - applyCount = 0; - _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u843d\u7a7a")); - } - } - - ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount); - upgradeUiState.totalChosenCount++; - TCHAR feedbackTitle[64]; - TCHAR feedbackDetail[128]; - _stprintf_s(feedbackTitle, _T("\u5df2\u83b7\u5f97\uff1a%s"), selectedOption.name); - if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0) - { - _stprintf_s( - feedbackDetail, - _T("\u964d\u4f4e %s \u65b9\u5757\u51fa\u73b0\u6982\u7387%s"), - GetPieceShortName(selectedOption.targetPieceType), - gamblerSuffix); - } - else - { - _stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix); - } - - if (selectedOption.cursed) - { - ApplyDestinyCurse(); - _stprintf_s( - feedbackDetail + lstrlen(feedbackDetail), - 128 - lstrlen(feedbackDetail), - _T(" \u8be5\u9009\u9879\u9644\u5e26\u8bc5\u5492\uff1a\u4e0b\u6b21\u5347\u7ea7\u6240\u9700 EXP \u63d0\u9ad8 25%\u3002")); - } - - SetFeedbackMessage(feedbackTitle, feedbackDetail, 12); - - if (upgradeUiState.picksRemaining > 0) - { - upgradeUiState.picksRemaining--; - } - - for (int i = upgradeUiState.selectedIndex; i + 1 < upgradeUiState.optionCount; i++) - { - upgradeUiState.options[i] = upgradeUiState.options[i + 1]; - } - if (upgradeUiState.optionCount > 0) - { - upgradeUiState.optionCount--; - } - - if (upgradeUiState.optionCount > 0 && upgradeUiState.selectedIndex >= upgradeUiState.optionCount) - { - upgradeUiState.selectedIndex = upgradeUiState.optionCount - 1; - } - - if (upgradeUiState.picksRemaining > 0 && upgradeUiState.optionCount > 0) - { - currentScreen = SCREEN_UPGRADE; - return; - } - - if (upgradeUiState.pendingCount > 0) - { - upgradeUiState.pendingCount--; - } - - if (upgradeUiState.pendingCount > 0) - { - FillUpgradeOptions(); - currentScreen = SCREEN_UPGRADE; - return; - } - - upgradeUiState.optionCount = 0; - upgradeUiState.picksRemaining = 0; - - currentScreen = SCREEN_PLAYING; -} - -void HoldCurrentPiece() -{ - if (currentMode != MODE_ROGUE || rogueStats.holdUnlocked == 0 || holdUsedThisTurn || gameOverFlag) - { - return; - } - - int previousHoldType = holdType; - holdType = type; - state = 0; - holdUsedThisTurn = true; - - if (previousHoldType < 0) - { - type = ConsumeNextType(); - nType = nextTypes[0]; - RollCurrentPieceSpecialFlags(true); - } - else - { - type = previousHoldType; - RollCurrentPieceSpecialFlags(false); - } - - point = GetSpawnPoint(type); - target = point; - if (currentMode == MODE_ROGUE && rogueStats.controlMasterLevel > 0) - { - rogueStats.holdSlowTicks = 4; - currentFallInterval = GetRogueFallInterval(); - } - - if (!IsPiecePlacementValid(type, state, point)) - { - gameOverFlag = true; - SetFeedbackMessage(_T("Hold \u5931\u8d25"), _T("\u6362\u51fa\u7684\u65b9\u5757\u65e0\u6cd5\u653e\u7f6e\uff0c\u5bf9\u5c40\u7ed3\u675f\u3002"), 12); - return; - } - - ComputeTarget(); - - if (previousHoldType < 0) - { - SetFeedbackMessage(_T("Hold \u5df2\u5b58\u5165"), _T("\u5f53\u524d\u65b9\u5757\u5df2\u8fdb\u5165 Hold \u69fd\u3002"), 10); - } - else - { - SetFeedbackMessage(_T("Hold \u5df2\u4ea4\u6362"), _T("\u5df2\u4e0e Hold \u69fd\u4e2d\u7684\u65b9\u5757\u4ea4\u6362\u3002"), 10); - } -} - -void UseScreenBomb() -{ - if (currentMode != MODE_ROGUE || rogueStats.screenBombCount <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) - { - return; - } - - rogueStats.screenBombCount--; - int clearedCells = TriggerScreenBomb(); - int scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100; - if (scoreGain < clearedCells) - { - scoreGain = clearedCells; - } - - rogueStats.score += scoreGain; - tScore = rogueStats.score; - - TCHAR detail[128]; - _stprintf_s( - detail, - _T("\u6e05\u9664\u5e95\u90e8 5 \u884c\uff0c\u6e05\u6389 %d \u683c +%d Score"), - clearedCells, - scoreGain); - SetFeedbackMessage(_T("\u4e3b\u52a8\u91ca\u653e\u6e05\u5c4f\u70b8\u5f39"), detail, 12); - - currentFallInterval = GetRogueFallInterval(); - ComputeTarget(); -} - -void UseBlackHole() -{ - if (currentMode != MODE_ROGUE || rogueStats.blackHoleCharges <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) - { - return; - } - - int clearedCells = TriggerBlackHole(); - if (clearedCells <= 0) - { - SetFeedbackMessage(_T("黑洞未触发"), _T("当前棋盘上没有可被黑洞吞噬的固定方块。"), 10); - return; - } - - rogueStats.blackHoleCharges--; - - int scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100; - if (scoreGain < clearedCells) - { - scoreGain = clearedCells; - } - - rogueStats.score += scoreGain; - tScore = rogueStats.score; - - if (rogueStats.voidCoreLevel > 0) - { - rogueStats.pendingRainbowPieceCount++; - } - - TCHAR detail[128]; - 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(); - ComputeTarget(); -} - -void UseAirReshape() -{ - if (currentMode != MODE_ROGUE || rogueStats.reshapeCharges <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) - { - return; - } - - const int targetType = 0; - const int candidateStates[2] = { 0, 1 }; - const int candidateOffsets[5] = { 0, -1, 1, -2, 2 }; - Point originalPoint = point; - int originalType = type; - int originalState = state; - bool transformed = false; - - for (int stateIndex = 0; stateIndex < 2 && !transformed; stateIndex++) - { - int nextState = candidateStates[stateIndex]; - for (int offsetIndex = 0; offsetIndex < 5; offsetIndex++) - { - Point candidatePoint = originalPoint; - candidatePoint.x += candidateOffsets[offsetIndex]; - - if (IsPiecePlacementValid(targetType, nextState, candidatePoint)) - { - type = targetType; - state = nextState; - point = candidatePoint; - transformed = true; - break; - } - } - } - - if (!transformed) - { - type = originalType; - state = originalState; - point = originalPoint; - SetFeedbackMessage(_T("空中换形失败"), _T("当前空间不足,无法将方块变为 I 方块。"), 10); - return; - } - - rogueStats.reshapeCharges--; - - TCHAR detail[128]; - _stprintf_s(detail, _T("当前方块已变为 I 方块,剩余 %d 次。"), rogueStats.reshapeCharges); - SetFeedbackMessage(_T("空中换形"), detail, 12); - ComputeTarget(); -} diff --git a/src/source/TetrisRogue.cpp b/src/source/TetrisRogue.cpp new file mode 100644 index 0000000..ae075ab --- /dev/null +++ b/src/source/TetrisRogue.cpp @@ -0,0 +1,1863 @@ +#include "stdafx.h" +#include "TetrisLogicInternal.h" + +enum UpgradeId +{ + UPGRADE_SCORE_MULTIPLIER = 0, + UPGRADE_EXP_MULTIPLIER = 1, + UPGRADE_SLOW_FALL = 2, + UPGRADE_COMBO_BONUS = 3, + UPGRADE_PREVIEW_PLUS_ONE = 4, + UPGRADE_LAST_CHANCE = 5, + UPGRADE_HOLD_UNLOCK = 6, + UPGRADE_PRESSURE_RELIEF = 7, + UPGRADE_SWEEPER = 8, + UPGRADE_EXPLOSIVE_PIECE = 9, + UPGRADE_STABLE_STRUCTURE = 10, + UPGRADE_DOUBLE_GROWTH = 11, + UPGRADE_PIECE_TUNING = 12, + UPGRADE_GAMBLER = 13, + UPGRADE_CHAIN_BLAST = 14, + UPGRADE_CHAIN_BOMB = 15, + UPGRADE_LASER_PIECE = 16, + UPGRADE_THUNDER_TETRIS = 17, + UPGRADE_THUNDER_LASER = 18, + UPGRADE_FEVER_MODE = 19, + UPGRADE_RAGE_STACK = 20, + UPGRADE_INFINITE_FEVER = 21, + UPGRADE_SCREEN_BOMB = 22, + UPGRADE_TERMINAL_CLEAR = 23, + UPGRADE_DUAL_CHOICE = 24, + UPGRADE_DESTINY_WHEEL = 25, + UPGRADE_PERFECT_ROTATE = 26, + UPGRADE_TIME_DILATION = 27, + UPGRADE_HIGH_PRESSURE = 28, + UPGRADE_TETRIS_GAMBLE = 29, + UPGRADE_EXTREME_PLAYER = 30, + UPGRADE_UPGRADE_SHOCKWAVE = 31, + UPGRADE_EVOLUTION_IMPACT = 32, + UPGRADE_CONTROL_MASTER = 33, + UPGRADE_BLOCK_STORM = 34, + UPGRADE_CROSS_PIECE = 35, + UPGRADE_BLACK_HOLE = 36, + UPGRADE_AIR_RESHAPE = 37, + UPGRADE_RAINBOW_PIECE = 38, + UPGRADE_VOID_CORE = 39 +}; + +static const UpgradeEntry kUpgradePool[] = +{ + { UPGRADE_SCORE_MULTIPLIER, -1, 100, true, _T("\u5206\u6570\u500d\u7387"), _T("\u5f97\u5206"), _T("\u6240\u6709\u5f97\u5206\u63d0\u9ad8 20%\u3002") }, + { UPGRADE_COMBO_BONUS, -1, 95, true, _T("\u8fde\u51fb\u52a0\u6210"), _T("\u8282\u594f"), _T("\u8fde\u7eed\u6d88\u884c\u65f6\u8ffd\u52a0\u8fde\u51fb\u5956\u52b1\u3002") }, + { UPGRADE_SLOW_FALL, -1, 90, true, _T("\u6162\u901f\u4e0b\u843d"), _T("\u64cd\u4f5c"), _T("\u81ea\u7136\u4e0b\u843d\u53d8\u6162\uff0c\u6bcf\u6b21\u63d0\u9ad8 80ms\u3002") }, + { UPGRADE_PREVIEW_PLUS_ONE, 3, 85, false, _T("\u989d\u5916\u9884\u89c8"), _T("\u89c6\u91ce"), _T("\u4e0b\u4e00\u4e2a\u65b9\u5757\u9884\u89c8 +1\uff0c\u6700\u591a 3 \u4e2a\u3002") }, + { UPGRADE_EXP_MULTIPLIER, -1, 100, true, _T("\u7ecf\u9a8c\u5f3a\u5316"), _T("\u6210\u957f"), _T("\u540e\u7eed\u6d88\u884c\u83b7\u5f97 EXP \u63d0\u9ad8 25%\u3002") }, + { UPGRADE_LAST_CHANCE, 1, 72, false, _T("\u6700\u540e\u4e00\u640f"), _T("\u4fdd\u547d"), _T("\u9996\u6b21\u9876\u6b7b\u65f6\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 3 \u884c\u5e76\u7ee7\u7eed\u6e38\u620f\u3002") }, + { UPGRADE_HOLD_UNLOCK, 1, 78, false, _T("Hold \u89e3\u9501"), _T("\u7279\u6b8a"), _T("\u89e3\u9501 Hold \u69fd\uff0c\u5bf9\u5c40\u4e2d\u53ef\u7528 C \u6216 Shift \u6682\u5b58\u65b9\u5757\u3002") }, + { UPGRADE_PRESSURE_RELIEF, -1, 82, true, _T("\u51cf\u538b"), _T("\u7279\u6b8a"), _T("\u7acb\u5373\u6e05\u9664\u5f53\u524d\u6700\u9ad8\u5360\u7528\u884c\uff0c\u4e3a\u76d8\u9762\u817e\u51fa\u547c\u5438\u7a7a\u95f4\u3002") }, + { UPGRADE_SWEEPER, -1, 74, true, _T("\u6e05\u626b\u8005"), _T("\u7279\u6b8a"), _T("\u7d2f\u8ba1\u6d88\u884c\u5145\u80fd\uff0c\u6536\u6ee1\u540e\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 1 \u884c\u3002") }, + { UPGRADE_EXPLOSIVE_PIECE, -1, 76, true, _T("\u7206\u7834\u65b9\u5757"), _T("\u7279\u6b8a"), _T("\u63d0\u9ad8\u7206\u7834\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u65f6\u89e6\u53d1 3x3 \u6e05\u9664\u3002") }, + { UPGRADE_CHAIN_BLAST, 1, 92, false, _T("\u8fde\u9501\u7206\u7834"), _T("\u8fdb\u9636"), _T("\u6d88\u884c\u540e\u5bf9\u88ab\u6e05\u9664\u884c\u9644\u8fd1\u968f\u673a\u6e05\u6389\u51e0\u683c\u3002") }, + { UPGRADE_CHAIN_BOMB, 1, 110, false, _T("\u8fde\u73af\u70b8\u5f39"), _T("\u8fdb\u5316"), _T("\u7206\u7834\u8303\u56f4\u63d0\u5347\u4e3a 5x5\uff0c\u82e5\u5f15\u53d1\u6d88\u884c\u5219\u8ffd\u52a0\u4e00\u6b21\u5c0f\u7206\u70b8\u3002") }, + { UPGRADE_LASER_PIECE, -1, 72, true, _T("\u6fc0\u5149\u65b9\u5757"), _T("\u7279\u6b8a"), _T("\u63d0\u9ad8\u6fc0\u5149\u65b9\u5757\u51fa\u73b0\u6982\u7387\uff0c\u843d\u5730\u540e\u6e05\u9664\u6240\u5728\u6574\u5217\u3002") }, + { UPGRADE_THUNDER_TETRIS, 1, 94, false, _T("\u96f7\u9706\u56db\u6d88"), _T("\u8fdb\u9636"), _T("\u6bcf\u6b21\u5b8c\u6210\u56db\u6d88\u65f6\uff0c\u989d\u5916\u6e05\u9664\u968f\u673a 2 \u884c\u3002") }, + { UPGRADE_THUNDER_LASER, 1, 112, false, _T("\u96f7\u9706\u6fc0\u5149"), _T("\u8fdb\u5316"), _T("\u56db\u6d88\u65f6\u989d\u5916\u53d1\u5c04 2 \u9053\u6fc0\u5149\uff0c\u968f\u673a\u6e05\u9664 2 \u5217\u5e76\u83b7\u5f97 EXP\u3002") }, + { UPGRADE_FEVER_MODE, 1, 84, false, _T("\u72c2\u70ed\u6a21\u5f0f"), _T("\u8fdb\u9636"), _T("\u7d2f\u8ba1\u6d88\u884c 20 \u884c\u540e\u8fdb\u5165 10 \u79d2\u72c2\u70ed\u72b6\u6001\u3002") }, + { UPGRADE_RAGE_STACK, 1, 84, false, _T("\u66b4\u8d70\u5806\u53e0"), _T("\u8fdb\u9636"), _T("\u8fde\u7eed\u6d88\u884c\u8d8a\u591a\uff0c\u5f97\u5206\u500d\u7387\u8ffd\u52a0\u8d8a\u9ad8\u3002") }, + { UPGRADE_INFINITE_FEVER, 1, 110, false, _T("\u65e0\u9650\u72c2\u70ed"), _T("\u8fdb\u5316"), _T("\u72c2\u70ed\u671f\u95f4\u6d88\u884c\u53ef\u5ef6\u957f\u65f6\u95f4\uff0c\u8fde\u51fb\u8d8a\u9ad8\u500d\u7387\u8d8a\u5f3a\u3002") }, + { UPGRADE_SCREEN_BOMB, 1, 78, false, _T("\u6e05\u5c4f\u70b8\u5f39"), _T("\u8fdb\u9636"), _T("\u7d2f\u8ba1\u6d88\u884c 30 \u884c\u540e\u83b7\u5f97 1 \u6b21\u6e05\u5c4f\u70b8\u5f39\uff0c\u53ef\u81ea\u52a8\u6216\u4e3b\u52a8\u6e05\u9664\u5e95\u90e8 5 \u884c\u3002") }, + { UPGRADE_TERMINAL_CLEAR, 1, 108, false, _T("\u7ec8\u672b\u6e05\u573a"), _T("\u8fdb\u5316"), _T("\u6fc0\u6d3b\u6700\u540e\u4e00\u640f\u65f6\u81ea\u52a8\u91ca\u653e\u4e00\u6b21\u6e05\u5c4f\u70b8\u5f39\uff0c\u5e76\u8fdb\u5165 10 \u79d2\u72c2\u70ed\u3002") }, + { UPGRADE_DUAL_CHOICE, 1, 68, false, _T("\u53cc\u91cd\u9009\u62e9"), _T("\u8fdb\u9636"), _T("\u6bcf\u6b21\u5347\u7ea7\u53ef\u989d\u5916\u518d\u9009 1 \u4e2a\u5f3a\u5316\uff0c\u4f46\u4e0b\u4e00\u6b21\u5347\u7ea7\u9700\u6c42 +30%\u3002") }, + { UPGRADE_DESTINY_WHEEL, 1, 104, false, _T("\u547d\u8fd0\u8f6e\u76d8"), _T("\u8fdb\u5316"), _T("\u6bcf\u6b21\u5347\u7ea7\u51fa\u73b0 5 \u4e2a\u9009\u9879\uff0c\u53ef\u9009 2 \u4e2a\uff0c\u4f46\u5176\u4e2d 1 \u4e2a\u9644\u5e26\u8d1f\u9762\u6548\u679c\u3002") }, + { UPGRADE_PERFECT_ROTATE, 1, 82, false, _T("\u5b8c\u7f8e\u65cb\u8f6c"), _T("\u64cd\u4f5c"), _T("\u65cb\u8f6c\u5931\u8d25\u65f6\u81ea\u52a8\u5c1d\u8bd5\u5de6\u53f3\u5404\u504f\u79fb 1 \u683c\u8fdb\u884c\u4fee\u6b63\u3002") }, + { UPGRADE_TIME_DILATION, 1, 80, false, _T("\u65f6\u95f4\u7f13\u6d41"), _T("\u4fdd\u547d"), _T("\u76d8\u9762\u63a5\u8fd1\u9876\u7aef\u65f6\u81ea\u52a8\u964d\u4f4e\u4e0b\u843d\u901f\u5ea6\uff0c\u4e3a\u8865\u6551\u7559\u51fa\u53cd\u5e94\u7a7a\u95f4\u3002") }, + { UPGRADE_HIGH_PRESSURE, 1, 70, false, _T("\u9ad8\u538b\u5956\u52b1"), _T("\u98ce\u9669"), _T("\u4e0b\u843d\u901f\u5ea6\u63d0\u9ad8 15%\uff0c\u4f46\u5f97\u5206\u4e0e EXP \u989d\u5916\u63d0\u9ad8 50%\u3002") }, + { UPGRADE_TETRIS_GAMBLE, 1, 78, false, _T("\u8d4c\u547d\u56db\u6d88"), _T("\u98ce\u9669"), _T("\u666e\u901a 1~3 \u6d88\u6536\u76ca\u964d\u4f4e\uff0c\u4f46 4 \u6d88\u6536\u76ca\u66b4\u589e\u3002") }, + { UPGRADE_EXTREME_PLAYER, 1, 114, false, _T("\u6781\u9650\u73a9\u5bb6"), _T("\u8fdb\u5316"), _T("\u7ec4\u5408\u9ad8\u538b\u4e0e\u8d4c\u547d\u56db\u6d88\uff0c\u901f\u5ea6\u8fdb\u4e00\u6b65\u63d0\u9ad8\uff0c\u56db\u6d88\u540e\u77ed\u6682\u7f13\u901f\u3002") }, + { UPGRADE_UPGRADE_SHOCKWAVE, 1, 106, false, _T("\u5347\u7ea7\u51b2\u51fb\u6ce2"), _T("\u8fdb\u9636"), _T("\u5373\u5c06\u5347\u7ea7\u65f6\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 2 \u884c\uff0c\u5f3a\u5316\u5347\u7ea7\u8282\u594f\u53cd\u9988\u3002") }, + { UPGRADE_EVOLUTION_IMPACT, 1, 118, false, _T("\u8fdb\u5316\u51b2\u51fb"), _T("\u8fdb\u5316"), _T("\u5347\u7ea7\u65f6\u6e05\u9664\u5e95\u90e8 3 \u884c\uff0c\u5e76\u83b7\u5f97 10 \u79d2\u53cc\u500d EXP\u3002") }, + { UPGRADE_CONTROL_MASTER, 1, 112, false, _T("\u64cd\u63a7\u5927\u5e08"), _T("\u8fdb\u5316"), _T("Hold \u540e\u77ed\u6682\u964d\u4f4e\u4e0b\u843d\u901f\u5ea6\uff0c\u5e76\u989d\u5916\u589e\u52a0 1 \u4e2a\u9884\u89c8\u65b9\u5757\u3002") }, + { UPGRADE_BLOCK_STORM, 1, 82, false, _T("\u65b9\u5757\u98ce\u66b4"), _T("\u723d\u611f"), _T("\u63a5\u4e0b\u6765 5 \u4e2a\u65b9\u5757\u5168\u90e8\u53d8\u6210 I \u5757\uff0c\u5feb\u901f\u5236\u9020\u56db\u6d88\u673a\u4f1a\u3002") }, + { 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") }, + { UPGRADE_GAMBLER, -1, 64, true, _T("\u8d4c\u5f92"), _T("\u7279\u6b8a"), _T("\u9009\u62e9\u5f3a\u5316\u65f6\uff0c\u6709\u6982\u7387\u53cc\u500d\u751f\u6548\uff0c\u4e5f\u6709\u6982\u7387\u5b8c\u5168\u843d\u7a7a\u3002") } +}; + +static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]); + +static int GetUpgradeCurrentLevel(int upgradeId); +static int GetUpgradeDynamicWeight(const UpgradeEntry& entry); +static const TCHAR* GetPieceShortName(int pieceType); +static bool IsUpgradeSelectable(const UpgradeEntry& entry); +static int GetTopOccupiedRow(); +static int GetSweeperThreshold(); +static bool RollExplosivePiece(); +static bool RollLaserPiece(); +static bool RollCrossPiece(); +static bool RollRainbowPiece(); +static int TriggerBlackHole(); +static int TriggerChainBlast(int lineAnchor); +static int RollNextPieceType(); +static int GetRogueScoreByLines(int linesCleared); +static int GetRogueExpByLines(int linesCleared); +static int ApplyLevelProgress(PlayerStats& stats); +static int TriggerUpgradeShockwave(int rowsToClear); +static void FillUpgradeOptions(); +static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount); +static void ApplyDestinyCurse(); + +static int GetNextPreviewLimit() +{ + if (currentMode != MODE_ROGUE) + { + return 1; + } + + if (rogueStats.previewCount < 1) + { + return 1; + } + + if (rogueStats.previewCount > 3) + { + return 3; + } + + return rogueStats.previewCount; +} + +static int GetUpgradeCurrentLevel(int upgradeId) +{ + switch (upgradeId) + { + case UPGRADE_SCORE_MULTIPLIER: + return rogueStats.scoreUpgradeLevel; + case UPGRADE_EXP_MULTIPLIER: + return rogueStats.expUpgradeLevel; + case UPGRADE_SLOW_FALL: + return rogueStats.slowFallStacks; + case UPGRADE_COMBO_BONUS: + return rogueStats.comboBonusStacks; + case UPGRADE_PREVIEW_PLUS_ONE: + return rogueStats.previewUpgradeLevel; + case UPGRADE_LAST_CHANCE: + return rogueStats.lastChanceUpgradeLevel; + case UPGRADE_HOLD_UNLOCK: + return rogueStats.holdUnlocked; + case UPGRADE_PRESSURE_RELIEF: + return rogueStats.pressureReliefLevel; + case UPGRADE_SWEEPER: + return rogueStats.sweeperLevel; + case UPGRADE_EXPLOSIVE_PIECE: + return rogueStats.explosiveLevel; + case UPGRADE_CHAIN_BLAST: + return rogueStats.chainBlastLevel; + case UPGRADE_CHAIN_BOMB: + return rogueStats.chainBombLevel; + case UPGRADE_LASER_PIECE: + return rogueStats.laserLevel; + case UPGRADE_THUNDER_TETRIS: + return rogueStats.thunderTetrisLevel; + case UPGRADE_THUNDER_LASER: + return rogueStats.thunderLaserLevel; + case UPGRADE_FEVER_MODE: + return rogueStats.feverLevel; + case UPGRADE_RAGE_STACK: + return rogueStats.rageStackLevel; + case UPGRADE_INFINITE_FEVER: + return rogueStats.infiniteFeverLevel; + case UPGRADE_SCREEN_BOMB: + return rogueStats.screenBombLevel; + case UPGRADE_TERMINAL_CLEAR: + return rogueStats.terminalClearLevel; + case UPGRADE_DUAL_CHOICE: + return rogueStats.dualChoiceLevel; + case UPGRADE_DESTINY_WHEEL: + return rogueStats.destinyWheelLevel; + case UPGRADE_PERFECT_ROTATE: + return rogueStats.perfectRotateLevel; + case UPGRADE_TIME_DILATION: + return rogueStats.timeDilationLevel; + case UPGRADE_HIGH_PRESSURE: + return rogueStats.highPressureLevel; + case UPGRADE_TETRIS_GAMBLE: + return rogueStats.tetrisGambleLevel; + case UPGRADE_EXTREME_PLAYER: + return rogueStats.extremePlayerLevel; + case UPGRADE_UPGRADE_SHOCKWAVE: + return rogueStats.upgradeShockwaveLevel; + case UPGRADE_EVOLUTION_IMPACT: + return rogueStats.evolutionImpactLevel; + case UPGRADE_CONTROL_MASTER: + return rogueStats.controlMasterLevel; + case UPGRADE_BLOCK_STORM: + return rogueStats.blockStormLevel; + case UPGRADE_CROSS_PIECE: + return rogueStats.crossPieceLevel; + case UPGRADE_BLACK_HOLE: + 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: + return rogueStats.doubleGrowthLevel; + case UPGRADE_GAMBLER: + return rogueStats.gamblerLevel; + default: + return 0; + } +} + +static int GetUpgradeDynamicWeight(const UpgradeEntry& entry) +{ + int weight = entry.baseWeight; + + if (entry.id == UPGRADE_PRESSURE_RELIEF) + { + int topRow = GetTopOccupiedRow(); + if (topRow >= 0) + { + int occupiedHeight = nGameHeight - topRow; + if (occupiedHeight >= 14) + { + weight += 50; + } + else if (occupiedHeight <= 7) + { + weight -= 25; + } + } + } + + if (entry.id == UPGRADE_LAST_CHANCE && rogueStats.lastChanceCount > 0) + { + weight -= 40; + } + + if (entry.id == UPGRADE_SLOW_FALL && rogueStats.slowFallStacks >= 4) + { + weight -= 20; + } + + if (entry.id == UPGRADE_SCREEN_BOMB && rogueStats.screenBombCount > 0) + { + weight += 20; + } + + if (entry.id == UPGRADE_BLACK_HOLE && rogueStats.blackHoleCharges > 0) + { + weight += 20; + } + + if (entry.id == UPGRADE_AIR_RESHAPE && rogueStats.reshapeCharges > 0) + { + weight += 20; + } + + if (entry.id == UPGRADE_CHAIN_BOMB && rogueStats.explosiveLevel == 0) + { + weight -= 60; + } + + if (entry.id == UPGRADE_THUNDER_LASER && rogueStats.thunderTetrisLevel == 0) + { + weight -= 80; + } + + if (entry.id == UPGRADE_INFINITE_FEVER && rogueStats.feverLevel == 0) + { + weight -= 80; + } + + if (entry.id == UPGRADE_VOID_CORE && + (rogueStats.blackHoleLevel == 0 || rogueStats.rainbowPieceLevel == 0)) + { + weight -= 90; + } + + return weight < 1 ? 1 : weight; +} + +static const TCHAR* GetPieceShortName(int pieceType) +{ + static const TCHAR* kNames[7] = + { + _T("I"), _T("T"), _T("L"), _T("J"), _T("O"), _T("S"), _T("Z") + }; + + if (pieceType < 0 || pieceType >= 7) + { + return _T("?"); + } + + return kNames[pieceType]; +} + +static bool IsUpgradeSelectable(const UpgradeEntry& entry) +{ + if (entry.id == UPGRADE_CHAIN_BOMB) + { + return rogueStats.explosiveLevel > 0 && + rogueStats.chainBombLevel == 0; + } + + if (entry.id == UPGRADE_THUNDER_LASER) + { + return rogueStats.thunderTetrisLevel > 0 && + rogueStats.thunderLaserLevel == 0; + } + + if (entry.id == UPGRADE_INFINITE_FEVER) + { + return rogueStats.feverLevel > 0 && + rogueStats.infiniteFeverLevel == 0; + } + + if (entry.id == UPGRADE_CONTROL_MASTER) + { + return rogueStats.perfectRotateLevel > 0 && + rogueStats.holdUnlocked > 0 && + rogueStats.controlMasterLevel == 0; + } + + if (entry.id == UPGRADE_VOID_CORE) + { + return rogueStats.blackHoleLevel > 0 && + rogueStats.rainbowPieceLevel > 0 && + rogueStats.voidCoreLevel == 0; + } + + if (entry.id == UPGRADE_EVOLUTION_IMPACT) + { + return rogueStats.upgradeShockwaveLevel > 0 && + rogueStats.doubleGrowthLevel > 0 && + rogueStats.evolutionImpactLevel == 0; + } + + if (entry.repeatable) + { + return true; + } + + if (entry.maxLevel <= 0) + { + return GetUpgradeCurrentLevel(entry.id) == 0; + } + + return GetUpgradeCurrentLevel(entry.id) < entry.maxLevel; +} + +static int GetTopOccupiedRow() +{ + for (int i = 0; i < nGameHeight; i++) + { + for (int j = 0; j < nGameWidth; j++) + { + if (workRegion[i][j] != 0) + { + return i; + } + } + } + + return -1; +} + +static int GetSweeperThreshold() +{ + int reduction = (rogueStats.sweeperLevel - 1) * 2; + int threshold = 8 - reduction; + return threshold < 3 ? 3 : threshold; +} + +static bool RollExplosivePiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.explosiveLevel <= 0) + { + return false; + } + + if (rogueStats.explosivePieceCounter < 10) + { + rogueStats.explosivePieceCounter++; + } + + if (rogueStats.explosivePieceCounter < 10) + { + return false; + } + + rogueStats.explosivePieceCounter = 0; + return true; +} + +static bool RollLaserPiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.laserLevel <= 0) + { + return false; + } + + int chancePercent = 10 + (rogueStats.laserLevel - 1) * 8; + if (chancePercent > 35) + { + chancePercent = 35; + } + + if (rogueStats.feverTicks > 0) + { + chancePercent += 10; + if (chancePercent > 50) + { + chancePercent = 50; + } + } + + return (rand() % 100) < chancePercent; +} + +static bool RollCrossPiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.crossPieceLevel <= 0) + { + return false; + } + + int chancePercent = 8 + (rogueStats.crossPieceLevel - 1) * 6; + if (chancePercent > 28) + { + chancePercent = 28; + } + + if (rogueStats.feverTicks > 0) + { + chancePercent += 10; + if (chancePercent > 40) + { + chancePercent = 40; + } + } + + return (rand() % 100) < chancePercent; +} + +static bool RollRainbowPiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0) + { + return false; + } + + int chancePercent = 12; + if (rogueStats.feverTicks > 0) + { + chancePercent += 10; + } + return (rand() % 100) < chancePercent; +} + +bool IsRainbowBoardCell(int cellValue) +{ + return cellValue == 8; +} + +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; +} + +void RollCurrentPieceSpecialFlags(bool allowRandomSpecials) +{ + if (!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(); +} + +int ClearExplosiveAreaAt(int centerY, int centerX) +{ + int radius = (currentMode == MODE_ROGUE && rogueStats.chainBombLevel > 0) ? 2 : 1; + int clearedCellCount = 0; + + for (int y = centerY - radius; y <= centerY + radius; y++) + { + for (int x = centerX - radius; x <= centerX + radius; x++) + { + if (y >= 0 && y < nGameHeight && x >= 0 && x < nGameWidth) + { + if (workRegion[y][x] != 0) + { + clearedCellCount++; + workRegion[y][x] = 0; + } + } + } + } + + return clearedCellCount; +} + +int ClearColumnAt(int column) +{ + int clearedCellCount = 0; + + if (column < 0 || column >= nGameWidth) + { + return 0; + } + + for (int y = 0; y < nGameHeight; y++) + { + if (workRegion[y][column] != 0) + { + workRegion[y][column] = 0; + clearedCellCount++; + } + } + + return clearedCellCount; +} + +int ClearRowAt(int row) +{ + int clearedCellCount = 0; + + if (row < 0 || row >= nGameHeight) + { + return 0; + } + + for (int x = 0; x < nGameWidth; x++) + { + if (workRegion[row][x] != 0) + { + workRegion[row][x] = 0; + clearedCellCount++; + } + } + + return clearedCellCount; +} + +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 }; + 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) + { + return 0; + } + + int clearedCellCount = 0; + for (int y = 0; y < nGameHeight; y++) + { + for (int x = 0; x < nGameWidth; x++) + { + if (workRegion[y][x] == targetBlock) + { + workRegion[y][x] = 0; + clearedCellCount++; + } + } + } + + return clearedCellCount; +} + +int TriggerScreenBomb() +{ + int clearedCellCount = 0; + + for (int i = 0; i < 5; i++) + { + int row = nGameHeight - 1 - i; + if (row < 0) + { + break; + } + + for (int x = 0; x < nGameWidth; x++) + { + if (workRegion[row][x] != 0) + { + clearedCellCount++; + } + } + + DeleteOneLine(row); + } + + return clearedCellCount; +} + +static int TriggerChainBlast(int lineAnchor) +{ + if (currentMode != MODE_ROGUE || rogueStats.chainBlastLevel <= 0) + { + return 0; + } + + int clearedCellCount = 0; + int blastCount = 2 + rogueStats.chainBlastLevel; + + for (int blastIndex = 0; blastIndex < blastCount; blastIndex++) + { + int randomY = lineAnchor + (rand() % 3) - 1; + int randomX = rand() % nGameWidth; + + if (randomY < 0) + { + randomY = 0; + } + if (randomY >= nGameHeight) + { + randomY = nGameHeight - 1; + } + + if (workRegion[randomY][randomX] != 0) + { + workRegion[randomY][randomX] = 0; + clearedCellCount++; + } + } + + return clearedCellCount; +} + +static int RollNextPieceType() +{ + if (currentMode == MODE_ROGUE && rogueStats.blockStormPiecesRemaining > 0) + { + rogueStats.blockStormPiecesRemaining--; + return 0; + } + + int weights[7] = { 100, 100, 100, 100, 100, 100, 100 }; + + if (currentMode == MODE_ROGUE) + { + for (int i = 0; i < 7; i++) + { + weights[i] -= rogueStats.pieceTuningLevels[i] * 20; + if (weights[i] < 20) + { + weights[i] = 20; + } + } + } + + int totalWeight = 0; + for (int i = 0; i < 7; i++) + { + totalWeight += weights[i]; + } + + int roll = rand() % totalWeight; + for (int i = 0; i < 7; i++) + { + if (roll < weights[i]) + { + return i; + } + roll -= weights[i]; + } + + return rand() % 7; +} + +int TryStabilizeBoard() +{ + if (currentMode != MODE_ROGUE || rogueStats.stableStructureLevel <= 0) + { + return 0; + } + + int triggerChance = 15 + (rogueStats.stableStructureLevel - 1) * 10; + if (triggerChance > 50) + { + triggerChance = 50; + } + + if ((rand() % 100) >= triggerChance) + { + return 0; + } + + for (int y = nGameHeight - 2; y >= 1; y--) + { + for (int x = 1; x < nGameWidth - 1; x++) + { + if (workRegion[y][x] != 0) + { + continue; + } + + bool hasSupportBelow = workRegion[y + 1][x] != 0; + bool hasLeftNeighbor = workRegion[y][x - 1] != 0; + bool hasRightNeighbor = workRegion[y][x + 1] != 0; + + if (hasSupportBelow && hasLeftNeighbor && hasRightNeighbor) + { + int fillValue = workRegion[y + 1][x]; + if (fillValue == 0) + { + fillValue = hasLeftNeighbor ? workRegion[y][x - 1] : workRegion[y][x + 1]; + } + workRegion[y][x] = fillValue; + return 1; + } + } + } + + return 0; +} + +void ResetNextQueue() +{ + for (int i = 0; i < 3; i++) + { + nextTypes[i] = RollNextPieceType(); + } +} + +int ConsumeNextType() +{ + int nextType = nextTypes[0]; + nextTypes[0] = nextTypes[1]; + nextTypes[1] = nextTypes[2]; + nextTypes[2] = RollNextPieceType(); + return nextType; +} + +static int GetRogueScoreByLines(int linesCleared) +{ + switch (linesCleared) + { + case 1: return 100; + case 2: return 300; + case 3: return 500; + case 4: return 800; + default: return 0; + } +} + +static int GetRogueExpByLines(int linesCleared) +{ + switch (linesCleared) + { + case 1: return 10; + case 2: return 25; + case 3: return 45; + case 4: return 80; + default: return 0; + } +} + +static int ApplyLevelProgress(PlayerStats& stats) +{ + int levelUps = 0; + + while (stats.requiredExp > 0 && stats.exp >= stats.requiredExp) + { + stats.exp -= stats.requiredExp; + stats.level++; + stats.requiredExp = 20 + stats.level * 10; + levelUps++; + } + + return levelUps; +} + +static int TriggerUpgradeShockwave(int rowsToClear) +{ + int clearedRows = 0; + + for (int i = 0; i < rowsToClear; i++) + { + DeleteOneLine(nGameHeight - 1); + clearedRows++; + } + + return clearedRows; +} + +static void FillUpgradeOptions() +{ + int selectableIndexes[kUpgradePoolSize] = { 0 }; + int selectableWeights[kUpgradePoolSize] = { 0 }; + int selectableCount = 0; + + for (int i = 0; i < kUpgradePoolSize; i++) + { + if (IsUpgradeSelectable(kUpgradePool[i])) + { + selectableIndexes[selectableCount] = i; + selectableWeights[selectableCount] = GetUpgradeDynamicWeight(kUpgradePool[i]); + selectableCount++; + } + } + + int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 5 : 3; + int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit; + upgradeUiState.optionCount = optionCount; + upgradeUiState.selectedIndex = 0; + upgradeUiState.picksRemaining = (rogueStats.dualChoiceLevel > 0 || rogueStats.destinyWheelLevel > 0) ? 2 : 1; + + 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 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].cursed = false; + upgradeUiState.options[i].name = pickedEntry.name; + upgradeUiState.options[i].category = pickedEntry.category; + upgradeUiState.options[i].description = pickedEntry.description; + + if (pickedEntry.id == UPGRADE_PIECE_TUNING) + { + int targetPieceType = rand() % 7; + bool duplicatedPiece = true; + int guard = 0; + + while (duplicatedPiece && guard < 16) + { + duplicatedPiece = false; + for (int optionIndex = 0; optionIndex < i; optionIndex++) + { + if (upgradeUiState.options[optionIndex].id == UPGRADE_PIECE_TUNING && + upgradeUiState.options[optionIndex].targetPieceType == targetPieceType) + { + duplicatedPiece = true; + targetPieceType = (targetPieceType + 1) % 7; + break; + } + } + guard++; + } + + upgradeUiState.options[i].targetPieceType = targetPieceType; + upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[targetPieceType]; + upgradeUiState.options[i].name = _T("\u65b9\u5757\u6539\u9020"); + + static TCHAR tuningDescriptions[4][64]; + _stprintf_s( + tuningDescriptions[i], + _T("\u964d\u4f4e %s \u65b9\u5757\u7684\u51fa\u73b0\u6982\u7387\u3002"), + GetPieceShortName(targetPieceType)); + upgradeUiState.options[i].description = tuningDescriptions[i]; + } + + 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; + } +} + +int GetRogueFallInterval() +{ + int baseInterval = 500 + rogueStats.slowFallStacks * 80; + + if (rogueStats.highPressureLevel > 0) + { + baseInterval = baseInterval * 85 / 100; + } + + if (rogueStats.extremePlayerLevel > 0) + { + baseInterval = baseInterval * 90 / 100; + } + + if (rogueStats.extremeDangerLevel > 0) + { + baseInterval = baseInterval * (100 - rogueStats.extremeDangerLevel * 8) / 100; + } + + if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0) + { + baseInterval += 120; + } + + if (rogueStats.timeDilationTicks > 0) + { + baseInterval = baseInterval * 130 / 100; + } + + if (rogueStats.extremeSlowTicks > 0) + { + baseInterval += 180; + } + + if (rogueStats.holdSlowTicks > 0) + { + baseInterval += 140; + } + + if (baseInterval < 120) + { + baseInterval = 120; + } + + return baseInterval; +} + +static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) +{ + if (applyCount <= 0) + { + 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; + currentFallInterval = GetRogueFallInterval(); + break; + case UPGRADE_PREVIEW_PLUS_ONE: + for (int i = 0; i < applyCount; i++) + { + if (rogueStats.previewCount < 3) + { + rogueStats.previewCount++; + } + } + rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; + break; + case UPGRADE_LAST_CHANCE: + rogueStats.lastChanceCount += applyCount; + rogueStats.lastChanceUpgradeLevel += applyCount; + break; + case UPGRADE_HOLD_UNLOCK: + rogueStats.holdUnlocked = 1; + 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; + break; + case UPGRADE_EXPLOSIVE_PIECE: + rogueStats.explosiveLevel += applyCount; + break; + case UPGRADE_CHAIN_BLAST: + rogueStats.chainBlastLevel = 1; + break; + case UPGRADE_CHAIN_BOMB: + rogueStats.chainBombLevel = 1; + break; + case UPGRADE_LASER_PIECE: + rogueStats.laserLevel += applyCount; + 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 = 1; + 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: + rogueStats.blockStormLevel = 1; + rogueStats.blockStormPiecesRemaining = 2; + nextTypes[0] = 0; + nextTypes[1] = 0; + nextTypes[2] = 0; + break; + case UPGRADE_CROSS_PIECE: + rogueStats.crossPieceLevel += applyCount; + break; + case UPGRADE_BLACK_HOLE: + rogueStats.blackHoleLevel = 1; + rogueStats.blackHoleCharges++; + break; + case UPGRADE_AIR_RESHAPE: + 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; + case UPGRADE_DOUBLE_GROWTH: + rogueStats.doubleGrowthLevel = 1; + rogueStats.scoreMultiplierPercent += 15; + rogueStats.expMultiplierPercent += 15; + break; + case UPGRADE_PIECE_TUNING: + if (targetPieceType >= 0 && targetPieceType < 7) + { + rogueStats.pieceTuningLevels[targetPieceType] += applyCount; + } + break; + case UPGRADE_GAMBLER: + rogueStats.gamblerLevel += applyCount; + break; + default: + break; + } +} + +void ApplyLineClearResult(int linesCleared) +{ + if (linesCleared <= 0) + { + if (currentMode == MODE_ROGUE) + { + rogueStats.comboChain = 0; + } + return; + } + + if (currentMode == MODE_CLASSIC) + { + classicStats.totalLinesCleared += linesCleared; + classicStats.score += linesCleared * 100; + tScore = classicStats.score; + return; + } + + int scoreGain = GetRogueScoreByLines(linesCleared); + scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100; + + int expGain = GetRogueExpByLines(linesCleared); + expGain = expGain * rogueStats.expMultiplierPercent / 100; + + if (rogueStats.tetrisGambleLevel > 0) + { + if (linesCleared == 4) + { + scoreGain *= 4; + } + else + { + scoreGain = scoreGain * 50 / 100; + } + } + + if (rogueStats.doubleGrowthLevel > 0) + { + int growthMultiplierPercent = 100 + rogueStats.doubleGrowthLevel * 15; + scoreGain = scoreGain * growthMultiplierPercent / 100; + expGain = expGain * growthMultiplierPercent / 100; + } + + if (rogueStats.feverTicks > 0) + { + scoreGain *= 2; + expGain *= 2; + } + + int gamblerBonusPercent = 0; + if (rogueStats.gamblerLevel > 0) + { + int variance = 20 + (rogueStats.gamblerLevel - 1) * 10; + if (variance > 50) + { + variance = 50; + } + + gamblerBonusPercent = (rand() % (variance * 2 + 1)) - variance; + scoreGain = scoreGain * (100 + gamblerBonusPercent) / 100; + expGain = expGain * (100 + gamblerBonusPercent) / 100; + } + + rogueStats.comboChain++; + if (rogueStats.comboBonusStacks > 0 && rogueStats.comboChain > 1) + { + scoreGain += (rogueStats.comboChain - 1) * rogueStats.comboBonusStacks * 50; + } + + if (rogueStats.rageStackLevel > 0 && rogueStats.comboChain > 1) + { + int rageBonusPercent = (rogueStats.comboChain - 1) * 15; + if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0) + { + rageBonusPercent += (rogueStats.comboChain - 1) * 10; + } + scoreGain += scoreGain * rageBonusPercent / 100; + } + + rogueStats.totalLinesCleared += linesCleared; + rogueStats.score += scoreGain; + rogueStats.exp += expGain; + + if (rogueStats.feverLevel > 0) + { + rogueStats.feverLineCharge += linesCleared; + while (rogueStats.feverLineCharge >= 20) + { + rogueStats.feverLineCharge -= 20; + rogueStats.feverTicks = 10; + currentFallInterval = GetRogueFallInterval(); + SetFeedbackMessage(_T("\u8fdb\u5165\u72c2\u70ed\u6a21\u5f0f"), _T("\u672a\u6765 10 \u79d2\u5f97\u5206 / EXP x2\uff0c\u4e0b\u843d\u66f4\u6162\uff0c\u7279\u6b8a\u65b9\u5757\u66f4\u6613\u51fa\u73b0\u3002"), 12); + } + } + + if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0) + { + rogueStats.feverTicks += 1; + if (linesCleared == 4) + { + rogueStats.feverTicks += 3; + } + if (rogueStats.feverTicks > 20) + { + rogueStats.feverTicks = 20; + } + currentFallInterval = GetRogueFallInterval(); + } + + if (rogueStats.extremePlayerLevel > 0 && linesCleared == 4) + { + rogueStats.extremeSlowTicks = 5; + rogueStats.extremeDangerTicks = 30; + currentFallInterval = GetRogueFallInterval(); + SetFeedbackMessage(_T("\u6781\u9650\u73a9\u5bb6\u89e6\u53d1"), _T("\u56db\u6d88\u6210\u529f\uff0c\u63a5\u4e0b\u6765 5 \u79d2\u77ed\u6682\u7f13\u901f\u8865\u4f4d\u3002"), 12); + } + + if (rogueStats.screenBombLevel > 0) + { + rogueStats.screenBombCharge += linesCleared; + while (rogueStats.screenBombCharge >= 30) + { + rogueStats.screenBombCharge -= 30; + rogueStats.screenBombCount++; + SetFeedbackMessage(_T("\u6e05\u5c4f\u70b8\u5f39\u5c31\u7eea"), _T("\u5df2\u83b7\u5f97 1 \u6b21\u6e05\u9664\u5e95\u90e8 5 \u884c\u7684\u673a\u4f1a\u3002"), 12); + } + } + + if (rogueStats.chainBlastLevel > 0) + { + int chainBlastCells = 0; + for (int i = 0; i < linesCleared; i++) + { + chainBlastCells += TriggerChainBlast(nGameHeight - 1 - i); + } + + if (chainBlastCells > 0) + { + int chainBlastScore = chainBlastCells * rogueStats.scoreMultiplierPercent / 100; + if (chainBlastScore < chainBlastCells) + { + chainBlastScore = chainBlastCells; + } + rogueStats.score += chainBlastScore; + tScore = rogueStats.score; + + TCHAR blastDetail[128]; + _stprintf_s( + blastDetail, + _T("\u989d\u5916\u6e05\u9664 %d \u683c +%d Score"), + chainBlastCells, + chainBlastScore); + SetFeedbackMessage(_T("\u8fde\u9501\u7206\u7834\u89e6\u53d1"), blastDetail, 12); + } + } + + if (linesCleared == 4 && rogueStats.thunderTetrisLevel > 0) + { + int thunderRowsCleared = 0; + for (int i = 0; i < 2; i++) + { + int randomRow = rand() % nGameHeight; + for (int x = 0; x < nGameWidth; x++) + { + if (workRegion[randomRow][x] != 0) + { + workRegion[randomRow][x] = 0; + thunderRowsCleared++; + } + } + } + + if (thunderRowsCleared > 0) + { + int thunderScore = thunderRowsCleared * rogueStats.scoreMultiplierPercent / 100; + if (thunderScore < thunderRowsCleared) + { + thunderScore = thunderRowsCleared; + } + rogueStats.score += thunderScore; + tScore = rogueStats.score; + SetFeedbackMessage(_T("\u96f7\u9706\u56db\u6d88\u89e6\u53d1"), _T("\u56db\u6d88\u540e\u989d\u5916\u6e05\u7406\u4e86 2 \u884c\u8303\u56f4\u5185\u7684\u683c\u5b50\u3002"), 12); + } + } + + if (linesCleared == 4 && rogueStats.thunderLaserLevel > 0) + { + int laserCellsCleared = 0; + for (int i = 0; i < 2; i++) + { + laserCellsCleared += ClearColumnAt(rand() % nGameWidth); + } + + if (laserCellsCleared > 0) + { + int laserScore = laserCellsCleared * rogueStats.scoreMultiplierPercent / 100; + int laserExp = laserCellsCleared * rogueStats.expMultiplierPercent / 100; + if (laserScore < laserCellsCleared) + { + laserScore = laserCellsCleared; + } + if (laserExp < laserCellsCleared) + { + laserExp = laserCellsCleared; + } + + rogueStats.score += laserScore; + rogueStats.exp += laserExp; + tScore = rogueStats.score; + + TCHAR thunderLaserDetail[128]; + _stprintf_s( + thunderLaserDetail, + _T("\u968f\u673a\u6fc0\u5149\u6e05\u9664 %d \u683c +%d Score +%d EXP"), + laserCellsCleared, + laserScore, + laserExp); + SetFeedbackMessage(_T("\u96f7\u9706\u6fc0\u5149\u89e6\u53d1"), thunderLaserDetail, 12); + } + } + + if (rogueStats.sweeperLevel > 0) + { + rogueStats.sweeperCharge += linesCleared; + int sweeperThreshold = GetSweeperThreshold(); + int clearedBySweeper = 0; + + while (rogueStats.sweeperCharge >= sweeperThreshold) + { + rogueStats.sweeperCharge -= sweeperThreshold; + DeleteOneLine(nGameHeight - 1); + clearedBySweeper++; + } + + if (clearedBySweeper > 0) + { + TCHAR sweeperDetail[128]; + _stprintf_s( + sweeperDetail, + _T("\u81ea\u52a8\u6e05\u7406\u5e95\u90e8 %d \u884c \u5269\u4f59\u5145\u80fd %d/%d"), + clearedBySweeper, + rogueStats.sweeperCharge, + sweeperThreshold); + SetFeedbackMessage(_T("\u6e05\u626b\u8005\u89e6\u53d1"), sweeperDetail, 12); + } + } + + int levelUps = ApplyLevelProgress(rogueStats); + upgradeUiState.pendingCount += levelUps; + tScore = rogueStats.score; + + if (levelUps > 0) + { + int shockwaveRows = 0; + if (rogueStats.evolutionImpactLevel > 0) + { + shockwaveRows = 3; + rogueStats.feverTicks = 10; + currentFallInterval = GetRogueFallInterval(); + } + else if (rogueStats.upgradeShockwaveLevel > 0) + { + shockwaveRows = 2; + } + + if (shockwaveRows > 0) + { + int clearedRows = TriggerUpgradeShockwave(shockwaveRows); + TCHAR shockwaveDetail[128]; + + if (rogueStats.evolutionImpactLevel > 0) + { + _stprintf_s( + shockwaveDetail, + _T("\u5347\u7ea7\u540c\u65f6\u6e05\u6389\u5e95\u90e8 %d \u884c\uff0c\u5e76\u8fdb\u5165 10 \u79d2\u53cc\u500d EXP\u72b6\u6001\u3002"), + clearedRows); + SetFeedbackMessage(_T("\u8fdb\u5316\u51b2\u51fb\u89e6\u53d1"), shockwaveDetail, 14); + } + else + { + _stprintf_s(shockwaveDetail, _T("\u5373\u5c06\u5347\u7ea7\uff0c\u63d0\u524d\u6e05\u6389\u5e95\u90e8 %d \u884c\u3002"), clearedRows); + SetFeedbackMessage(_T("\u5347\u7ea7\u51b2\u51fb\u6ce2\u89e6\u53d1"), shockwaveDetail, 12); + } + } + + TCHAR feedbackTitle[64]; + TCHAR feedbackDetail[128]; + _stprintf_s(feedbackTitle, _T("Level Up x%d"), levelUps); + _stprintf_s( + feedbackDetail, + _T("\u5f53\u524d Lv.%d EXP %d/%d \u5373\u5c06\u8fdb\u5165\u5f3a\u5316\u9009\u62e9"), + rogueStats.level, + rogueStats.exp, + rogueStats.requiredExp); + SetFeedbackMessage(feedbackTitle, feedbackDetail, 10); + } + + currentFallInterval = GetRogueFallInterval(); +} + +static void ApplyDestinyCurse() +{ + rogueStats.requiredExp = rogueStats.requiredExp * 125 / 100; + if (rogueStats.requiredExp < 10) + { + rogueStats.requiredExp = 10; + } +} + +void OpenUpgradeMenu() +{ + if (currentMode != MODE_ROGUE || upgradeUiState.pendingCount <= 0) + { + return; + } + + FillUpgradeOptions(); + currentScreen = SCREEN_UPGRADE; +} + +void ConfirmUpgradeSelection() +{ + if (currentScreen != SCREEN_UPGRADE || upgradeUiState.optionCount <= 0) + { + return; + } + + UpgradeOption selectedOption = upgradeUiState.options[upgradeUiState.selectedIndex]; + int applyCount = 1; + TCHAR gamblerSuffix[64] = _T(""); + + if (currentMode == MODE_ROGUE && rogueStats.gamblerLevel > 0) + { + int gamblerChance = 20 + (rogueStats.gamblerLevel - 1) * 5; + if (gamblerChance > 40) + { + gamblerChance = 40; + } + + int roll = rand() % 100; + if (roll < gamblerChance) + { + applyCount = 2; + _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u53cc\u500d")); + } + else if (roll >= 100 - gamblerChance) + { + applyCount = 0; + _stprintf_s(gamblerSuffix, _T(" \u8d4c\u5f92\uff1a\u843d\u7a7a")); + } + } + + ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount); + upgradeUiState.totalChosenCount++; + TCHAR feedbackTitle[64]; + TCHAR feedbackDetail[128]; + _stprintf_s(feedbackTitle, _T("\u5df2\u83b7\u5f97\uff1a%s"), selectedOption.name); + if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0) + { + _stprintf_s( + feedbackDetail, + _T("\u964d\u4f4e %s \u65b9\u5757\u51fa\u73b0\u6982\u7387%s"), + GetPieceShortName(selectedOption.targetPieceType), + gamblerSuffix); + } + else + { + _stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix); + } + + if (selectedOption.cursed) + { + ApplyDestinyCurse(); + _stprintf_s( + feedbackDetail + lstrlen(feedbackDetail), + 128 - lstrlen(feedbackDetail), + _T(" \u8be5\u9009\u9879\u9644\u5e26\u8bc5\u5492\uff1a\u4e0b\u6b21\u5347\u7ea7\u6240\u9700 EXP \u63d0\u9ad8 25%\u3002")); + } + + SetFeedbackMessage(feedbackTitle, feedbackDetail, 12); + + if (upgradeUiState.picksRemaining > 0) + { + upgradeUiState.picksRemaining--; + } + + for (int i = upgradeUiState.selectedIndex; i + 1 < upgradeUiState.optionCount; i++) + { + upgradeUiState.options[i] = upgradeUiState.options[i + 1]; + } + if (upgradeUiState.optionCount > 0) + { + upgradeUiState.optionCount--; + } + + if (upgradeUiState.optionCount > 0 && upgradeUiState.selectedIndex >= upgradeUiState.optionCount) + { + upgradeUiState.selectedIndex = upgradeUiState.optionCount - 1; + } + + if (upgradeUiState.picksRemaining > 0 && upgradeUiState.optionCount > 0) + { + currentScreen = SCREEN_UPGRADE; + return; + } + + if (upgradeUiState.pendingCount > 0) + { + upgradeUiState.pendingCount--; + } + + if (upgradeUiState.pendingCount > 0) + { + FillUpgradeOptions(); + currentScreen = SCREEN_UPGRADE; + return; + } + + upgradeUiState.optionCount = 0; + upgradeUiState.picksRemaining = 0; + + currentScreen = SCREEN_PLAYING; +} + +void HoldCurrentPiece() +{ + if (currentMode != MODE_ROGUE || rogueStats.holdUnlocked == 0 || holdUsedThisTurn || gameOverFlag) + { + return; + } + + int previousHoldType = holdType; + holdType = type; + state = 0; + holdUsedThisTurn = true; + + if (previousHoldType < 0) + { + type = ConsumeNextType(); + nType = nextTypes[0]; + RollCurrentPieceSpecialFlags(true); + } + else + { + type = previousHoldType; + RollCurrentPieceSpecialFlags(false); + } + + point = GetSpawnPoint(type); + target = point; + if (currentMode == MODE_ROGUE && rogueStats.controlMasterLevel > 0) + { + rogueStats.holdSlowTicks = 4; + currentFallInterval = GetRogueFallInterval(); + } + + if (!IsPiecePlacementValid(type, state, point)) + { + gameOverFlag = true; + SetFeedbackMessage(_T("Hold \u5931\u8d25"), _T("\u6362\u51fa\u7684\u65b9\u5757\u65e0\u6cd5\u653e\u7f6e\uff0c\u5bf9\u5c40\u7ed3\u675f\u3002"), 12); + return; + } + + ComputeTarget(); + + if (previousHoldType < 0) + { + SetFeedbackMessage(_T("Hold \u5df2\u5b58\u5165"), _T("\u5f53\u524d\u65b9\u5757\u5df2\u8fdb\u5165 Hold \u69fd\u3002"), 10); + } + else + { + SetFeedbackMessage(_T("Hold \u5df2\u4ea4\u6362"), _T("\u5df2\u4e0e Hold \u69fd\u4e2d\u7684\u65b9\u5757\u4ea4\u6362\u3002"), 10); + } +} + +void UseScreenBomb() +{ + if (currentMode != MODE_ROGUE || rogueStats.screenBombCount <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) + { + return; + } + + rogueStats.screenBombCount--; + int clearedCells = TriggerScreenBomb(); + int scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100; + if (scoreGain < clearedCells) + { + scoreGain = clearedCells; + } + + rogueStats.score += scoreGain; + tScore = rogueStats.score; + + TCHAR detail[128]; + _stprintf_s( + detail, + _T("\u6e05\u9664\u5e95\u90e8 5 \u884c\uff0c\u6e05\u6389 %d \u683c +%d Score"), + clearedCells, + scoreGain); + SetFeedbackMessage(_T("\u4e3b\u52a8\u91ca\u653e\u6e05\u5c4f\u70b8\u5f39"), detail, 12); + + currentFallInterval = GetRogueFallInterval(); + ComputeTarget(); +} + +void UseBlackHole() +{ + if (currentMode != MODE_ROGUE || rogueStats.blackHoleCharges <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) + { + return; + } + + int clearedCells = TriggerBlackHole(); + if (clearedCells <= 0) + { + SetFeedbackMessage(_T("黑洞未触发"), _T("当前棋盘上没有可被黑洞吞噬的固定方块。"), 10); + return; + } + + rogueStats.blackHoleCharges--; + + int scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100; + if (scoreGain < clearedCells) + { + scoreGain = clearedCells; + } + + rogueStats.score += scoreGain; + tScore = rogueStats.score; + + if (rogueStats.voidCoreLevel > 0) + { + rogueStats.pendingRainbowPieceCount++; + } + + TCHAR detail[128]; + 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(); + ComputeTarget(); +} + +void UseAirReshape() +{ + if (currentMode != MODE_ROGUE || rogueStats.reshapeCharges <= 0 || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag) + { + return; + } + + const int targetType = 0; + const int candidateStates[2] = { 0, 1 }; + const int candidateOffsets[5] = { 0, -1, 1, -2, 2 }; + Point originalPoint = point; + int originalType = type; + int originalState = state; + bool transformed = false; + + for (int stateIndex = 0; stateIndex < 2 && !transformed; stateIndex++) + { + int nextState = candidateStates[stateIndex]; + for (int offsetIndex = 0; offsetIndex < 5; offsetIndex++) + { + Point candidatePoint = originalPoint; + candidatePoint.x += candidateOffsets[offsetIndex]; + + if (IsPiecePlacementValid(targetType, nextState, candidatePoint)) + { + type = targetType; + state = nextState; + point = candidatePoint; + transformed = true; + break; + } + } + } + + if (!transformed) + { + type = originalType; + state = originalState; + point = originalPoint; + SetFeedbackMessage(_T("空中换形失败"), _T("当前空间不足,无法将方块变为 I 方块。"), 10); + return; + } + + rogueStats.reshapeCharges--; + + TCHAR detail[128]; + _stprintf_s(detail, _T("当前方块已变为 I 方块,剩余 %d 次。"), rogueStats.reshapeCharges); + SetFeedbackMessage(_T("空中换形"), detail, 12); + ComputeTarget(); +}