diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 22d97da..4d3c637 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -68,6 +68,8 @@ struct PlayerStats int screenBombCharge; int screenBombCount; int terminalClearLevel; + int dualChoiceLevel; + int destinyWheelLevel; int stableStructureLevel; int doubleGrowthLevel; int gamblerLevel; @@ -79,6 +81,7 @@ struct UpgradeOption int id; int currentLevel; int targetPieceType; + bool cursed; const TCHAR* name; const TCHAR* category; const TCHAR* description; @@ -88,6 +91,7 @@ struct UpgradeEntry { int id; int maxLevel; + int baseWeight; bool repeatable; const TCHAR* name; const TCHAR* category; @@ -100,7 +104,8 @@ struct UpgradeUiState int optionCount; int pendingCount; int totalChosenCount; - UpgradeOption options[4]; + int picksRemaining; + UpgradeOption options[5]; }; struct FeedbackState @@ -169,5 +174,6 @@ void OpenRulesScreen(); void OpenUpgradeMenu(); void ConfirmUpgradeSelection(); void HoldCurrentPiece(); +void UseScreenBomb(); void TDrawScreen(HDC hdc, HWND hWnd); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 8e34f47..02ee169 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -419,6 +419,9 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case VK_SHIFT: HoldCurrentPiece(); break; + case 'X': + UseScreenBomb(); + break; default: break; } diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index ee4e05d..d8fb335 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -52,38 +52,43 @@ enum UpgradeId UPGRADE_RAGE_STACK = 20, UPGRADE_INFINITE_FEVER = 21, UPGRADE_SCREEN_BOMB = 22, - UPGRADE_TERMINAL_CLEAR = 23 + UPGRADE_TERMINAL_CLEAR = 23, + UPGRADE_DUAL_CHOICE = 24, + UPGRADE_DESTINY_WHEEL = 25 }; static const UpgradeEntry kUpgradePool[] = { - { UPGRADE_SCORE_MULTIPLIER, -1, true, _T("\u5206\u6570\u500d\u7387"), _T("\u5f97\u5206"), _T("\u6240\u6709\u5f97\u5206\u63d0\u9ad8 20%\u3002") }, - { UPGRADE_COMBO_BONUS, -1, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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_STABLE_STRUCTURE, -1, 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, true, _T("\u53cc\u500d\u6210\u957f"), _T("\u7279\u6b8a"), _T("\u989d\u5916\u63d0\u9ad8\u6d88\u884c\u5f97\u5206\u4e0e EXP \u6536\u76ca\uff0c\u6bcf\u5c42\u518d\u8ffd\u52a0 15%\u3002") }, - { UPGRADE_PIECE_TUNING, -1, 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, 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") } + { 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_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, 86, true, _T("\u53cc\u500d\u6210\u957f"), _T("\u7279\u6b8a"), _T("\u989d\u5916\u63d0\u9ad8\u6d88\u884c\u5f97\u5206\u4e0e EXP \u6536\u76ca\uff0c\u6bcf\u5c42\u518d\u8ffd\u52a0 15%\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] = { @@ -250,6 +255,8 @@ static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) stats.screenBombCharge = 0; stats.screenBombCount = 0; stats.terminalClearLevel = 0; + stats.dualChoiceLevel = 0; + stats.destinyWheelLevel = 0; stats.stableStructureLevel = 0; stats.doubleGrowthLevel = 0; stats.gamblerLevel = 0; @@ -323,6 +330,10 @@ static int GetUpgradeCurrentLevel(int upgradeId) 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_STABLE_STRUCTURE: return rogueStats.stableStructureLevel; case UPGRADE_DOUBLE_GROWTH: @@ -334,6 +345,175 @@ static int GetUpgradeCurrentLevel(int upgradeId) } } +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_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] = @@ -402,6 +582,16 @@ static bool IsUpgradeSelectable(const UpgradeEntry& entry) 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.repeatable) { return true; @@ -751,29 +941,57 @@ static int ApplyLevelProgress(PlayerStats& stats) 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; + selectableIndexes[selectableCount] = i; + selectableWeights[selectableCount] = GetUpgradeDynamicWeight(kUpgradePool[i]); + selectableCount++; } } - int optionCount = selectableCount < 4 ? selectableCount : 4; + int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 5 : 4; + 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 pickSlot = rand() % selectableCount; + int totalWeight = 0; + for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) + { + totalWeight += selectableWeights[weightIndex]; + } + + int pickSlot = 0; + if (totalWeight > 0) + { + int roll = rand() % totalWeight; + int accumulatedWeight = 0; + + for (int weightIndex = 0; weightIndex < selectableCount; weightIndex++) + { + accumulatedWeight += selectableWeights[weightIndex]; + if (roll < accumulatedWeight) + { + pickSlot = weightIndex; + break; + } + } + } + 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; @@ -813,8 +1031,15 @@ static void FillUpgradeOptions() } 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; + } } static int GetRogueFallInterval() @@ -917,6 +1142,17 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) 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_STABLE_STRUCTURE: rogueStats.stableStructureLevel += applyCount; break; @@ -1204,6 +1440,17 @@ static void ApplyLineClearResult(int linesCleared) } } +static void ApplyDestinyCurse() +{ + int expPenalty = rogueStats.requiredExp * 25 / 100; + if (expPenalty < 10) + { + expPenalty = 10; + } + + rogueStats.requiredExp += expPenalty; +} + /** * @brief 判断当前方块是否可以继续向下移动。 * @@ -1738,6 +1985,7 @@ void Restart() upgradeUiState.optionCount = 0; upgradeUiState.pendingCount = 0; upgradeUiState.totalChosenCount = 0; + upgradeUiState.picksRemaining = 0; feedbackState.visibleTicks = 0; feedbackState.title[0] = _T('\0'); feedbackState.detail[0] = _T('\0'); @@ -1776,6 +2024,7 @@ void ReturnToMainMenu() gameOverFlag = false; menuState.optionCount = 3; upgradeUiState.pendingCount = 0; + upgradeUiState.picksRemaining = 0; if (menuState.selectedIndex < 0 || menuState.selectedIndex >= menuState.optionCount) { @@ -1849,8 +2098,43 @@ void ConfirmUpgradeSelection() { _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--; @@ -1863,6 +2147,9 @@ void ConfirmUpgradeSelection() return; } + upgradeUiState.optionCount = 0; + upgradeUiState.picksRemaining = 0; + currentScreen = SCREEN_PLAYING; } @@ -1913,3 +2200,32 @@ void HoldCurrentPiece() 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); + + ComputeTarget(); +} diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 8cb0d24..fd81974 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -748,7 +748,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) if (rogueStats.screenBombLevel > 0) { TCHAR bombText[96]; - _stprintf_s(bombText, _T("\u6e05\u5c4f\u70b8\u5f39 %d / 30 \u5e93\u5b58 %d"), rogueStats.screenBombCharge, rogueStats.screenBombCount); + _stprintf_s(bombText, _T("\u6e05\u5c4f\u70b8\u5f39 %d / 30 \u5e93\u5b58 %d X \u4e3b\u52a8\u91ca\u653e"), rogueStats.screenBombCharge, rogueStats.screenBombCount); TextOut(hdc, combatRect.left + SS(18), combatRect.top + SS(530), bombText, lstrlen(bombText)); } @@ -934,6 +934,14 @@ void TDrawScreen(HDC hdc, HWND hWnd) { _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u8d4c\u5f92 Lv.%d\r\n"), rogueStats.gamblerLevel); } + if (rogueStats.dualChoiceLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u53cc\u91cd\u9009\u62e9 Lv.1\r\n")); + } + if (rogueStats.destinyWheelLevel > 0) + { + _stprintf_s(upgradeSummary + lstrlen(upgradeSummary), 512 - lstrlen(upgradeSummary), _T("\u547d\u8fd0\u8f6e\u76d8 Lv.1\r\n")); + } if (lstrlen(upgradeSummary) == 0) { _stprintf_s(upgradeSummary, _T("\u6682\u672a\u9009\u62e9\u4efb\u4f55\u5f3a\u5316\u3002\r\n\u5347\u7ea7\u540e\u4f1a\u5728\u8fd9\u91cc\u7d2f\u79ef\u663e\u793a\u3002")); @@ -1243,13 +1251,25 @@ void TDrawScreen(HDC hdc, HWND hWnd) overlayRect.right, overlayRect.top + SS(106) }; - DrawText(hdc, _T("\u8bf7\u4ece\u4e09\u4e2a\u9009\u9879\u4e2d\u9009\u62e9\u4e00\u4e2a\u5f3a\u5316"), -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + TCHAR overlaySubtitle[128]; + _stprintf_s( + overlaySubtitle, + _T("\u672c\u6b21\u51fa\u73b0 %d \u4e2a\u9009\u9879\uff0c\u8fd8\u53ef\u9009 %d \u4e2a"), + upgradeUiState.optionCount, + upgradeUiState.picksRemaining); + DrawText(hdc, overlaySubtitle, -1, &overlaySubtitleRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); int gap = SS(18); int horizontalPadding = SS(36); int verticalTop = overlayRect.top + SS(138); + int rowCount = (upgradeUiState.optionCount + 1) / 2; + if (rowCount < 1) + { + rowCount = 1; + } int cardWidth = (overlayRect.right - overlayRect.left - horizontalPadding * 2 - gap) / 2; - int cardHeight = (overlayRect.bottom - verticalTop - SS(72) - gap) / 2; + int availableHeight = overlayRect.bottom - verticalTop - SS(72) - (rowCount - 1) * gap; + int cardHeight = availableHeight / rowCount; for (int i = 0; i < upgradeUiState.optionCount; i++) { @@ -1268,8 +1288,31 @@ void TDrawScreen(HDC hdc, HWND hWnd) top + cardHeight }; - HBRUSH cardBrush = CreateSolidBrush(isSelected ? RGB(255, 235, 242) : RGB(255, 247, 250)); - HPEN cardPen = CreatePen(PS_SOLID, isSelected ? SS(3) : 1, isSelected ? accentColor : RGB(221, 197, 208)); + COLORREF cardFill = RGB(255, 247, 250); + COLORREF cardBorder = RGB(221, 197, 208); + COLORREF iconFill = RGB(233, 206, 217); + COLORREF categoryColor = RGB(122, 95, 110); + COLORREF descColor = RGB(108, 86, 99); + COLORREF footerColor = RGB(128, 104, 118); + + if (upgradeUiState.options[i].cursed) + { + cardFill = isSelected ? RGB(255, 233, 233) : RGB(255, 243, 243); + cardBorder = isSelected ? RGB(210, 92, 92) : RGB(224, 156, 156); + iconFill = isSelected ? RGB(235, 128, 128) : RGB(232, 182, 182); + categoryColor = RGB(146, 73, 73); + descColor = RGB(120, 74, 74); + footerColor = RGB(150, 70, 70); + } + else if (isSelected) + { + cardFill = RGB(255, 235, 242); + cardBorder = accentColor; + iconFill = RGB(242, 176, 202); + } + + HBRUSH cardBrush = CreateSolidBrush(cardFill); + HPEN cardPen = CreatePen(PS_SOLID, isSelected ? SS(3) : 1, cardBorder); oldPen = (HPEN)SelectObject(hdc, cardPen); oldBrush = (HBRUSH)SelectObject(hdc, cardBrush); RoundRect(hdc, cardRect.left, cardRect.top, cardRect.right, cardRect.bottom, SS(24), SS(24)); @@ -1286,7 +1329,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) cardRect.top + SS(84) }; - HBRUSH iconBrush = CreateSolidBrush(isSelected ? RGB(242, 176, 202) : RGB(233, 206, 217)); + HBRUSH iconBrush = CreateSolidBrush(iconFill); HPEN iconPen = CreatePen(PS_SOLID, 1, RGB(255, 250, 252)); oldPen = (HPEN)SelectObject(hdc, iconPen); oldBrush = (HBRUSH)SelectObject(hdc, iconBrush); @@ -1297,7 +1340,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DeleteObject(iconPen); SelectObject(hdc, smallFont); - SetTextColor(hdc, RGB(122, 95, 110)); + SetTextColor(hdc, categoryColor); RECT categoryRect = { cardRect.left + SS(20), @@ -1319,7 +1362,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DrawText(hdc, levelText, -1, &levelRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE); SelectObject(hdc, sectionFont); - SetTextColor(hdc, isSelected ? titleColor : textColor); + SetTextColor(hdc, upgradeUiState.options[i].cursed ? RGB(130, 54, 54) : (isSelected ? titleColor : textColor)); RECT nameRect = { cardRect.left + SS(20), @@ -1330,7 +1373,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DrawText(hdc, upgradeUiState.options[i].name, -1, &nameRect, DT_LEFT | DT_VCENTER | DT_WORDBREAK); SelectObject(hdc, bodyFont); - SetTextColor(hdc, RGB(108, 86, 99)); + SetTextColor(hdc, descColor); RECT descRect = { cardRect.left + SS(20), @@ -1348,8 +1391,13 @@ void TDrawScreen(HDC hdc, HWND hWnd) cardRect.right - SS(20), cardRect.bottom - SS(22) }; - SetTextColor(hdc, RGB(128, 104, 118)); - DrawText(hdc, _T("\u5360\u4f4d\u56fe\u6807 / \u5360\u4f4d\u7a00\u6709\u5ea6"), -1, &footerRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + SetTextColor(hdc, footerColor); + DrawText( + hdc, + upgradeUiState.options[i].cursed ? _T("\u547d\u8fd0\u8f6e\u76d8\uff1a\u9644\u5e26\u8bc5\u5492") : _T("\u5360\u4f4d\u56fe\u6807 / \u5360\u4f4d\u7a00\u6709\u5ea6"), + -1, + &footerRect, + DT_LEFT | DT_VCENTER | DT_SINGLELINE); } SelectObject(hdc, smallFont);