3008 lines
90 KiB
C++
3008 lines
90 KiB
C++
#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();
|
|
}
|