#include "stdafx.h" #include "Tetris.h" int nType = 0; int type = 0; int state = 0; int tScore = 0; bool gameOverFlag = false; bool suspendFlag = false; bool targetFlag = false; int workRegion[20][10] = { 0 }; Point point = { 0, 0 }; Point target = { 0, 0 }; MenuState menuState = { 0, 2 }; PlayerStats classicStats = { 0, 1, 0, 0, 0 }; PlayerStats rogueStats = { 0, 1, 0, 30, 0, 100, 100, 0 }; UpgradeUiState upgradeUiState = { 0, 0, 0, 0, {} }; FeedbackState feedbackState = { 0, _T(""), _T("") }; int currentScreen = SCREEN_MENU; int currentMode = MODE_CLASSIC; int currentFallInterval = 500; int nextTypes[3] = { 0, 0, 0 }; int holdType = -1; bool holdUsedThisTurn = false; bool currentPieceIsExplosive = false; bool currentPieceIsLaser = false; bool currentPieceIsCross = false; 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] = { { {{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, {{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}}, {{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}}, {{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}} }, { {{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 2, 2, 2}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 0, 2, 2}, {0, 0, 2, 0}}, {{0, 0, 0, 0}, {0, 0, 0, 0}, {0, 2, 2, 2}, {0, 0, 2, 0}}, {{0, 0, 0, 0}, {0, 0, 2, 0}, {0, 2, 2, 0}, {0, 0, 2, 0}} }, { {{0, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 3, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {3, 3, 3, 0}, {3, 0, 0, 0}, {0, 0, 0, 0}}, {{3, 3, 0, 0}, {0, 3, 0, 0}, {0, 3, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 3, 0}, {3, 3, 3, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} }, { {{0, 0, 4, 0}, {0, 0, 4, 0}, {0, 4, 4, 0}, {0, 0, 0, 0}}, {{0, 4, 0, 0}, {0, 4, 4, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 4, 4}, {0, 0, 4, 0}, {0, 0, 4, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 4, 4, 4}, {0, 0, 0, 4}, {0, 0, 0, 0}} }, { {{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 5, 5, 0}, {0, 5, 5, 0}, {0, 0, 0, 0}} }, { {{0, 6, 0, 0}, {0, 6, 6, 0}, {0, 0, 6, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 6, 6, 0}, {6, 6, 0, 0}, {0, 0, 0, 0}}, {{6, 0, 0, 0}, {6, 6, 0, 0}, {0, 6, 0, 0}, {0, 0, 0, 0}}, {{0, 6, 6, 0}, {6, 6, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}} }, { {{0, 0, 7, 0}, {0, 7, 7, 0}, {0, 7, 0, 0}, {0, 0, 0, 0}}, {{0, 7, 7, 0}, {0, 0, 7, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 7}, {0, 0, 7, 7}, {0, 0, 7, 0}, {0, 0, 0, 0}}, {{0, 0, 0, 0}, {0, 7, 7, 0}, {0, 0, 7, 7}, {0, 0, 0, 0}} } }; COLORREF BrickColor[7] = { RGB(244, 144, 165), RGB(255, 181, 197), RGB(170, 215, 255), RGB(134, 230, 220), RGB(255, 187, 143), RGB(255, 223, 146), RGB(197, 170, 255) }; /** * @brief 计算指定方块在指定旋转状态下的最小包围盒边界。 * * 该函数会遍历 4x4 形状矩阵,找出所有非空单元的上下左右边界, * 供后续统一计算生成位置和对齐方式时使用。 * * @param brickType 方块类型编号。 * @param brickState 方块旋转状态编号。 * @param minRow 返回最上方非空行号。 * @param maxRow 返回最下方非空行号。 * @param minCol 返回最左侧非空列号。 * @param maxCol 返回最右侧非空列号。 */ static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxRow, int& minCol, int& maxCol) { minRow = 4; maxRow = -1; minCol = 4; maxCol = -1; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[brickType][brickState][i][j] != 0) { if (i < minRow) { minRow = i; } if (i > maxRow) { maxRow = i; } if (j < minCol) { minCol = j; } if (j > maxCol) { maxCol = j; } } } } } /** * @brief 计算指定方块的统一生成位置。 * * 该函数会根据方块在初始旋转状态下的最小包围盒, * 自动把方块水平居中到游戏区附近,并将顶部非空行对齐到可视区域顶部。 * 这样不同形状的方块在生成时看起来会更加统一。 * * @param brickType 方块类型编号。 * @return Point 计算得到的生成坐标。 */ static Point GetSpawnPoint(int brickType) { int minRow, maxRow, minCol, maxCol; GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol); int brickWidth = maxCol - minCol + 1; int brickHeight = maxRow - minRow + 1; Point spawnPoint; spawnPoint.x = (nGameWidth - brickWidth) / 2 - minCol; spawnPoint.y = -brickHeight; return spawnPoint; } static void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) { stats.score = 0; stats.level = 1; stats.exp = 0; stats.requiredExp = useRogueRules ? 30 : 0; stats.totalLinesCleared = 0; stats.scoreMultiplierPercent = 100; stats.expMultiplierPercent = 100; stats.slowFallStacks = 0; stats.comboBonusStacks = 0; stats.comboChain = 0; stats.previewCount = 1; stats.lastChanceCount = 0; stats.scoreUpgradeLevel = 0; stats.expUpgradeLevel = 0; stats.previewUpgradeLevel = 0; stats.lastChanceUpgradeLevel = 0; stats.holdUnlocked = 0; stats.pressureReliefLevel = 0; stats.sweeperLevel = 0; stats.sweeperCharge = 0; stats.explosiveLevel = 0; stats.explosivePieceCounter = 0; stats.chainBlastLevel = 0; stats.chainBombLevel = 0; stats.laserLevel = 0; stats.thunderTetrisLevel = 0; stats.thunderLaserLevel = 0; stats.feverLevel = 0; stats.rageStackLevel = 0; stats.infiniteFeverLevel = 0; stats.feverLineCharge = 0; stats.feverTicks = 0; stats.screenBombLevel = 0; stats.screenBombCharge = 0; stats.screenBombCount = 0; stats.terminalClearLevel = 0; stats.dualChoiceLevel = 0; stats.destinyWheelLevel = 0; stats.perfectRotateLevel = 0; stats.timeDilationLevel = 0; stats.highPressureLevel = 0; stats.tetrisGambleLevel = 0; stats.extremePlayerLevel = 0; stats.extremeSlowTicks = 0; stats.upgradeShockwaveLevel = 0; stats.evolutionImpactLevel = 0; stats.controlMasterLevel = 0; stats.holdSlowTicks = 0; stats.blockStormLevel = 0; stats.blockStormPiecesRemaining = 0; stats.blackHoleLevel = 0; stats.blackHoleCharges = 0; stats.reshapeLevel = 0; stats.reshapeCharges = 0; stats.rainbowPieceLevel = 0; stats.voidCoreLevel = 0; stats.pendingRainbowPieceCount = 0; stats.stableStructureLevel = 0; stats.doubleGrowthLevel = 0; stats.gamblerLevel = 0; for (int i = 0; i < 7; i++) { stats.pieceTuningLevels[i] = 0; } } 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) { 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) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[pieceType][pieceState][i][j] == 0) { continue; } int checkY = position.y + i; int checkX = position.x + j; if (checkX < 0 || checkX >= nGameWidth || checkY >= nGameHeight) { return false; } if (checkY >= 0 && workRegion[checkY][checkX] != 0) { return false; } } } 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; } 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; } return (rand() % 100) < chancePercent; } static bool RollRainbowPiece() { if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0) { return false; } int chancePercent = 12; return (rand() % 100) < chancePercent; } static bool IsRainbowBoardCell(int cellValue) { return cellValue == 8; } static int TriggerMiniBlackHole(int maxCellsToClear) { int blockCounts[8] = { 0 }; for (int y = 0; y < nGameHeight; y++) { for (int x = 0; x < nGameWidth; x++) { int cell = workRegion[y][x]; if (cell >= 1 && cell <= 7) { blockCounts[cell]++; } } } int targetBlock = 0; int maxCount = 0; for (int block = 1; block <= 7; block++) { if (blockCounts[block] > maxCount) { maxCount = blockCounts[block]; targetBlock = block; } } if (targetBlock == 0 || maxCellsToClear <= 0) { return 0; } int clearedCellCount = 0; for (int y = nGameHeight - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--) { for (int x = 0; x < nGameWidth && clearedCellCount < maxCellsToClear; x++) { if (workRegion[y][x] == targetBlock) { workRegion[y][x] = 0; clearedCellCount++; } } } return clearedCellCount; } static void RollCurrentPieceSpecialFlags(bool allowRandomSpecials) { if (!allowRandomSpecials) { 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.timeDilationLevel > 0) { int topOccupiedRow = GetTopOccupiedRow(); if (topOccupiedRow >= 0 && topOccupiedRow <= 5) { baseInterval += 180; } } if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0) { baseInterval += 120; } 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; 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; expGain *= 3; } else { scoreGain = scoreGain * 60 / 100; expGain = expGain * 75 / 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\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; 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 判断当前方块是否可以继续向下移动。 * * 遍历当前处于活动状态下方块的 4x4 矩阵,计算其下落一步(Y 坐标加 1)后的位置, * 并检查每个非空方块单元: * 1. 是否超出了游戏工作区的底部边界(对应数组索引 >= 20)。 * 2. 是否与工作区下方已经固定的其他方块发生碰撞(即对应位置的值不为 0)。 * 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续下落。 * * @return bool 如果可以继续安全下落返回 true,否则返回 false。 */ bool CanMoveDown() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int nextY = point.y + i + 1; int nextX = point.x + j; // 检查是否到达底部边界 if (nextY >= nGameHeight) { return false; } // 检查下方是否有其他固定方块 if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } } } } return true; } /** * @brief 判断当前方块是否可以继续向左移动。 * * 遍历当前处于活动状态下方块的 4x4 矩阵,计算其向左移动一步(X 坐标减 1)后的位置, * 并检查每个非空方块单元: * 1. 是否超出了游戏工作区的左侧边界(对应数组索引 < 0)。 * 2. 是否与工作区左侧已经固定的其他方块发生碰撞(即对应位置的值不为 0)。 * 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续左移。 * * @return bool 如果可以继续安全左移返回 true,否则返回 false。 */ bool CanMoveLeft() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int nextY = point.y + i; int nextX = point.x + j - 1; // 检查是否到达左侧边界 if (nextX < 0) { return false; } // 检查左侧是否有其他固定方块 if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } } } } return true; } /** * @brief 判断当前方块是否可以继续向右移动。 * * 遍历当前处于活动状态下方块的 4x4 矩阵,计算其向右移动一步(X 坐标加 1)后的位置, * 并检查每个非空方块单元: * 1. 是否超出了游戏工作区的右侧边界(对应数组索引 >= 10)。 * 2. 是否与工作区右侧已经固定的其他方块发生碰撞(即对应位置的值不为 0)。 * 如果遇到以上任意一种情况,则认为方块受到阻挡,无法继续右移。 * * @return bool 如果可以继续安全右移返回 true,否则返回 false。 */ bool CanMoveRight() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int nextY = point.y + i; int nextX = point.x + j + 1; // 检查是否到达右侧边界 if (nextX >= nGameWidth) { return false; } // 检查右侧是否有其他固定方块 if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } } } } return true; } static bool TryRotateWithOffset(int nextState, int offsetX) { Point rotatedPoint = point; rotatedPoint.x += offsetX; return IsPiecePlacementValid(type, nextState, rotatedPoint); } /** * @brief 将当前活动方块向下移动一格。 * * 该函数只负责修改当前活动方块的纵坐标,将其在工作区中的位置向下推进 1 格。 * 是否允许下移由外部配合 CanMoveDown 函数提前判断。 */ void MoveDown() { // 当前方块下移一格 point.y++; } /** * @brief 将当前活动方块向左移动一格。 * * 该函数只负责修改当前活动方块的横坐标,将其在工作区中的位置向左推进 1 格。 * 是否允许左移由外部配合 CanMoveLeft 函数提前判断。 */ void MoveLeft() { // 当前方块左移一格 point.x--; } /** * @brief 将当前活动方块向右移动一格。 * * 该函数只负责修改当前活动方块的横坐标,将其在工作区中的位置向右推进 1 格。 * 是否允许右移由外部配合 CanMoveRight 函数提前判断。 */ void MoveRight() { // 当前方块右移一格 point.x++; } /** * @brief 旋转当前活动方块到下一种朝向。 * * 游戏中的每种方块都预置了 4 种旋转状态,该函数会先尝试切换到下一状态, * 然后检查旋转后的方块是否越界或与固定方块重叠。 * 如果旋转后的状态非法,则恢复到旋转前的状态。 */ void Rotate() { int nextState = (state + 1) % 4; if (IsPiecePlacementValid(type, nextState, point)) { state = nextState; return; } if (currentMode == MODE_ROGUE && rogueStats.perfectRotateLevel > 0) { if (TryRotateWithOffset(nextState, -1)) { state = nextState; point.x--; return; } if (TryRotateWithOffset(nextState, 1)) { state = nextState; point.x++; return; } } } /** * @brief 让当前活动方块快速下落到当前位置能够到达的最低点。 * * 该函数会持续检查当前方块是否还能继续下移,只要可以下移就重复调用 MoveDown, * 直到方块到达底部或被其他固定方块阻挡为止。 */ void DropDown() { // 只要还能继续下落,就不断下移 while (CanMoveDown()) { MoveDown(); } } /** * @brief 将当前活动方块固定到工作区,并生成下一个活动方块。 * * 遍历当前方块 4x4 形状矩阵,把其中所有非空单元写入工作区数组, * 表示该方块已经落地并转为固定状态。 * 如果固定时仍有任意非空单元位于可视区域顶部之外,则判定游戏结束。 * 此时当前方块在可视区域内的部分仍会保留在工作区中。 * 若未超出顶部,再将“下一方块”切换为新的当前方块,重置旋转状态, * 并把新方块生成到工作区上方的初始位置,同时刷新预测落点。 */ void Fixing() { bool overflowTop = false; Point explosiveCells[4] = {}; int explosiveCellCount = 0; int rainbowFilledCount = 0; pendingChainBombFollowup = false; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (bricks[type][state][i][j] != 0) { int fixY = point.y + i; int fixX = point.x + j; // 只要当前方块任意非空单元仍超出顶部,就标记为结束 if (fixY < 0) { overflowTop = true; } // 将当前方块在可视区域内的部分写入工作区 if (fixY >= 0 && fixY < nGameHeight && fixX >= 0 && fixX < nGameWidth) { workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j]; if (currentPieceIsExplosive && explosiveCellCount < 4) { explosiveCells[explosiveCellCount].x = fixX; explosiveCells[explosiveCellCount].y = fixY; explosiveCellCount++; } } } } } if (!overflowTop && currentPieceIsRainbow) { rainbowFilledCount = TriggerRainbowRowCompletion(point.y, point.y + 3); if (rainbowFilledCount > 0) { TCHAR rainbowDetail[128]; _stprintf_s(rainbowDetail, _T("\u8865\u5168 %d \u4e2a\u884c\u5185\u7f3a\u53e3\uff0c\u66f4\u5bb9\u6613\u8fbe\u6210\u6d88\u884c\u3002"), rainbowFilledCount); SetFeedbackMessage(_T("\u5f69\u8679\u65b9\u5757\u751f\u6548"), rainbowDetail, 10); } } if (overflowTop) { if (currentMode == MODE_ROGUE && rogueStats.terminalClearLevel > 0 && rogueStats.lastChanceCount > 0 && rogueStats.screenBombCount > 0) { rogueStats.lastChanceCount--; rogueStats.screenBombCount--; int clearedByTerminal = TriggerScreenBomb(); rogueStats.feverTicks = 10; currentFallInterval = GetRogueFallInterval(); TCHAR terminalDetail[128]; _stprintf_s( terminalDetail, _T("\u7ec8\u672b\u6e05\u573a\u751f\u6548\uff0c\u6e05\u9664 %d \u683c\uff0c\u5e76\u8fdb\u5165 10 \u79d2\u72c2\u70ed\u3002"), clearedByTerminal); SetFeedbackMessage(_T("\u7ec8\u672b\u6e05\u573a\u89e6\u53d1"), terminalDetail, 14); } else if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0) { rogueStats.lastChanceCount--; for (int i = 0; i < 3; i++) { DeleteOneLine(nGameHeight - 1); } SetFeedbackMessage( _T("\u6700\u540e\u4e00\u640f\u89e6\u53d1"), _T("\u81ea\u52a8\u6e05\u9664\u5e95\u90e8 3 \u884c\uff0c\u672c\u5c40\u7ee7\u7eed\u3002"), 14); } else { gameOverFlag = true; return; } } if (currentPieceIsExplosive) { int explosiveScoreGain = 0; for (int i = 0; i < explosiveCellCount; i++) { explosiveScoreGain += ClearExplosiveAreaAt(explosiveCells[i].y, explosiveCells[i].x); } if (currentMode == MODE_ROGUE && explosiveScoreGain > 0) { explosiveScoreGain = explosiveScoreGain * rogueStats.scoreMultiplierPercent / 100; rogueStats.score += explosiveScoreGain; tScore = rogueStats.score; } TCHAR explosiveDetail[128]; _stprintf_s( explosiveDetail, _T("\u6e05\u9664 %d \u4e2a\u683c\u5b50 +%d Score"), explosiveScoreGain > 0 ? explosiveScoreGain * 100 / rogueStats.scoreMultiplierPercent : 0, explosiveScoreGain); SetFeedbackMessage(_T("\u7206\u7834\u65b9\u5757\u89e6\u53d1"), explosiveDetail, 12); if (rogueStats.chainBombLevel > 0 && explosiveCellCount > 0) { pendingChainBombCenter = explosiveCells[0]; pendingChainBombFollowup = true; } } if (currentPieceIsLaser) { int laserColumn = point.x + 1; if (laserColumn < 0) { laserColumn = 0; } if (laserColumn >= nGameWidth) { laserColumn = nGameWidth - 1; } int laserCellsCleared = ClearColumnAt(laserColumn); if (currentMode == MODE_ROGUE && laserCellsCleared > 0) { int laserScore = laserCellsCleared * rogueStats.scoreMultiplierPercent / 100; if (laserScore < laserCellsCleared) { laserScore = laserCellsCleared; } rogueStats.score += laserScore; tScore = rogueStats.score; TCHAR laserDetail[128]; _stprintf_s(laserDetail, _T("\u6e05\u9664 %d \u683c +%d Score"), laserCellsCleared, laserScore); SetFeedbackMessage(_T("\u6fc0\u5149\u65b9\u5757\u89e6\u53d1"), laserDetail, 12); } } if (currentPieceIsCross) { int crossRow = point.y + 1; int crossColumn = point.x + 1; if (crossRow < 0) { crossRow = 0; } if (crossRow >= nGameHeight) { crossRow = nGameHeight - 1; } if (crossColumn < 0) { crossColumn = 0; } if (crossColumn >= nGameWidth) { crossColumn = nGameWidth - 1; } int crossCellsCleared = ClearRowAt(crossRow); int columnCellsCleared = ClearColumnAt(crossColumn); if (workRegion[crossRow][crossColumn] == 0 && columnCellsCleared > 0) { // center cell may already be counted by row clear } int totalCrossCleared = crossCellsCleared + columnCellsCleared; if (currentMode == MODE_ROGUE && totalCrossCleared > 0) { int crossScore = totalCrossCleared * rogueStats.scoreMultiplierPercent / 100; if (crossScore < totalCrossCleared) { crossScore = totalCrossCleared; } rogueStats.score += crossScore; tScore = rogueStats.score; TCHAR crossDetail[128]; _stprintf_s(crossDetail, _T("\u6e05\u9664 %d \u683c +%d Score"), totalCrossCleared, crossScore); SetFeedbackMessage(_T("\u5341\u5b57\u65b9\u5757\u89e6\u53d1"), crossDetail, 12); } } if (TryStabilizeBoard() > 0) { SetFeedbackMessage(_T("\u7a33\u5b9a\u7ed3\u6784\u751f\u6548"), _T("\u81ea\u52a8\u586b\u8865\u4e86\u4e00\u4e2a\u90bb\u8fd1\u7a7a\u6d1e\u3002"), 10); } if (currentMode == MODE_ROGUE) { currentFallInterval = GetRogueFallInterval(); } // 生成下一个活动方块 type = ConsumeNextType(); nType = nextTypes[0]; state = 0; holdUsedThisTurn = false; RollCurrentPieceSpecialFlags(true); point = GetSpawnPoint(type); target = point; ComputeTarget(); } /** * @brief 删除指定行,并让其上方所有行整体下移一格。 * * 该函数会先将目标行上方的所有数据逐行向下复制, * 再把最顶端一行清空,从而完成一次标准的消行下移操作。 * * @param number 需要被删除的目标行号。 */ void DeleteOneLine(int number) { for (int i = number; i > 0; i--) { for (int j = 0; j < nGameWidth; j++) { workRegion[i][j] = workRegion[i - 1][j]; } } // 清空最顶端一行 for (int j = 0; j < nGameWidth; j++) { workRegion[0][j] = 0; } } /** * @brief 检查并删除所有已满的行,同时更新当前得分。 * * 该函数会从底部向上遍历工作区,判断每一行是否被完全填满。 * 如果某一行全部非 0,则调用 DeleteOneLine 删除该行, * 并将该行上方的内容整体下移。为了避免连续满行被漏检, * 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。 */ int DeleteLines() { int clearedLines = 0; bool clearedWithRainbow = false; for (int i = nGameHeight - 1; i >= 0; i--) { bool fullLine = true; for (int j = 0; j < nGameWidth; j++) { if (workRegion[i][j] == 0) { fullLine = false; break; } } if (fullLine) { for (int j = 0; j < nGameWidth; j++) { if (IsRainbowBoardCell(workRegion[i][j])) { clearedWithRainbow = true; break; } } DeleteOneLine(i); clearedLines++; i++; } } ApplyLineClearResult(clearedLines); if (pendingChainBombFollowup && clearedLines > 0) { pendingChainBombFollowup = false; int followupCleared = 0; int centerY = pendingChainBombCenter.y; int centerX = pendingChainBombCenter.x; for (int y = centerY - 1; y <= centerY + 1; y++) { for (int x = centerX - 1; x <= centerX + 1; x++) { if (y >= 0 && y < nGameHeight && x >= 0 && x < nGameWidth && workRegion[y][x] != 0) { workRegion[y][x] = 0; followupCleared++; } } } if (currentMode == MODE_ROGUE && followupCleared > 0) { int followupScore = followupCleared * rogueStats.scoreMultiplierPercent / 100; if (followupScore < followupCleared) { followupScore = followupCleared; } rogueStats.score += followupScore; tScore = rogueStats.score; TCHAR followupDetail[128]; _stprintf_s( followupDetail, _T("\u8ffd\u52a0\u5c0f\u7206\u70b8\u6e05\u9664 %d \u683c +%d Score"), followupCleared, followupScore); SetFeedbackMessage(_T("\u8fde\u73af\u70b8\u5f39\u89e6\u53d1"), followupDetail, 12); } } else { pendingChainBombFollowup = false; } if (currentMode == MODE_ROGUE && clearedWithRainbow && rogueStats.voidCoreLevel > 0) { int miniBlackHoleCleared = TriggerMiniBlackHole(5); if (miniBlackHoleCleared > 0) { int miniScore = miniBlackHoleCleared * rogueStats.scoreMultiplierPercent / 100; if (miniScore < miniBlackHoleCleared) { miniScore = miniBlackHoleCleared; } rogueStats.score += miniScore; tScore = rogueStats.score; TCHAR miniDetail[128]; _stprintf_s(miniDetail, _T("\u5f69\u8679\u6d88\u884c\u89e6\u53d1\u5c0f\u578b\u9ed1\u6d1e\uff0c\u6e05\u9664 %d \u683c +%d Score"), miniBlackHoleCleared, miniScore); SetFeedbackMessage(_T("\u865a\u7a7a\u6838\u5fc3\u89e6\u53d1"), miniDetail, 12); } } return clearedLines; } /** * @brief 计算当前活动方块的预测落点位置。 * * 该函数以当前活动方块的位置为起点,使用虚拟下落的方式不断尝试向下移动, * 直到方块无法继续下落为止。最终得到的最低可达位置会写入 target, * 供后续界面绘制瞄准器或落点提示时使用。 * * 计算过程中不会改变当前方块的真实位置 point。 */ void ComputeTarget() { Point originalPoint = point; // 从当前方块位置开始向下试探 target = point; while (CanMoveDown()) { point.y++; target = point; } // 恢复当前方块的真实位置 point = originalPoint; } /** * @brief 重置整个游戏状态,开始新的一局。 * * 该函数会清空工作区中的所有固定方块数据,重置分数、结束标记和暂停标记, * 并重新初始化当前方块、下一方块、旋转状态以及生成位置。 * 最后会重新计算一次当前方块的预测落点。 */ void Restart() { for (int i = 0; i < nGameHeight; i++) { for (int j = 0; j < nGameWidth; j++) { workRegion[i][j] = 0; } } gameOverFlag = false; suspendFlag = false; targetFlag = true; currentFallInterval = 500; ResetPlayerStats(classicStats, false); ResetPlayerStats(rogueStats, true); upgradeUiState.selectedIndex = 0; 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'); holdType = -1; holdUsedThisTurn = false; RollCurrentPieceSpecialFlags(false); tScore = 0; ResetNextQueue(); type = ConsumeNextType(); nType = nextTypes[0]; state = 0; holdUsedThisTurn = false; RollCurrentPieceSpecialFlags(true); point = GetSpawnPoint(type); target = point; ComputeTarget(); } void StartGameWithMode(int mode) { currentMode = mode; currentScreen = SCREEN_PLAYING; Restart(); currentFallInterval = (currentMode == MODE_ROGUE) ? GetRogueFallInterval() : 500; tScore = (currentMode == MODE_CLASSIC) ? classicStats.score : rogueStats.score; } void ReturnToMainMenu() { currentScreen = SCREEN_MENU; suspendFlag = false; gameOverFlag = false; menuState.optionCount = 3; upgradeUiState.pendingCount = 0; upgradeUiState.picksRemaining = 0; if (menuState.selectedIndex < 0 || menuState.selectedIndex >= menuState.optionCount) { menuState.selectedIndex = 0; } } 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(); }