Files
Tereis/src/source/TetrisRogue.cpp
T
2026-04-26 18:31:56 +08:00

2670 lines
78 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "stdafx.h"
#include "TetrisLogicInternal.h"
/**
* @brief Rogue 创新玩法独立实现文件。
*
* 本文件集中放置从基础俄罗斯方块版本扩展出的创新设计,包括强化池、
* 等级成长、特殊方块、主动技能、难度收缩、连锁奖励和升级选择流程。
* 代码保持过程式函数组织,不使用 class、继承或多态。
*/
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("赏金纹章"), _T("得分"), _T("所有得分提高 20%,每次选择都会继续叠加。") },
{ UPGRADE_COMBO_BONUS, -1, 95, true, _T("连击律动"), _T("节奏"), _T("连续消行会追加连击奖励,节奏越稳收益越高。") },
{ UPGRADE_SLOW_FALL, -1, 90, true, _T("缓坠羽翼"), _T("操作"), _T("自然下落延缓 80ms,为摆放和补洞争取更多时间。") },
{ UPGRADE_PREVIEW_PLUS_ONE, 3, 85, false, _T("先见之眼"), _T("视野"), _T("下一个方块预览 +1,最多可同时看见 3 个。") },
{ UPGRADE_EXP_MULTIPLIER, -1, 100, true, _T("成长印记"), _T("成长"), _T("消行获得的 EXP 提高 25%,更快迎来下一次强化。") },
{ UPGRADE_LAST_CHANCE, 1, 72, false, _T("最后一搏"), _T("保命"), _T("首次濒临失败时清除底部 3 行,让本局继续战斗。") },
{ UPGRADE_HOLD_UNLOCK, 1, 78, false, _T("备用仓"), _T("特殊"), _T("解锁 Hold。按 C 或 Shift 暂存下落方块,每个方块落地前限用一次。") },
{ UPGRADE_PRESSURE_RELIEF, -1, 82, true, _T("卸压清场"), _T("特殊"), _T("立刻清除最高占用行,为棋盘腾出一段喘息空间。") },
{ UPGRADE_SWEEPER, -1, 74, true, _T("底线清道夫"), _T("特殊"), _T("消行会为清道夫充能,充满后自动清除底部 1 行。") },
{ UPGRADE_EXPLOSIVE_PIECE, -1, 86, true, _T("爆破核心"), _T("特殊"), _T("大幅提高爆破方块出现率。爆破方块落地时清除 3x3 区域。") },
{ UPGRADE_CHAIN_BLAST, 1, 92, false, _T("连锁火花"), _T("进阶"), _T("每次消行后,在被清除行附近追加随机破坏。") },
{ UPGRADE_CHAIN_BOMB, 1, 110, false, _T("连环炸弹"), _T("进化"), _T("爆破范围扩大为 5x5;若引发消行,再追加一次小爆炸。") },
{ UPGRADE_LASER_PIECE, -1, 84, true, _T("棱镜激光"), _T("特殊"), _T("大幅提高激光方块出现率。激光方块落地后清除所在整列。") },
{ UPGRADE_THUNDER_TETRIS, 1, 94, false, _T("雷霆四消"), _T("进阶"), _T("完成三消或四消时,额外轰击随机 2 行。") },
{ UPGRADE_THUNDER_LASER, 1, 112, false, _T("雷霆棱镜"), _T("进化"), _T("三消或四消时额外发射 2 道激光,随机清除 2 列并获得 EXP。") },
{ UPGRADE_FEVER_MODE, 1, 92, false, _T("狂热节拍"), _T("进阶"), _T("累计消行 12 行后进入 12 秒狂热:得分与 EXP 翻倍。") },
{ UPGRADE_RAGE_STACK, 1, 84, false, _T("怒火连段"), _T("进阶"), _T("连续消行越多,得分倍率追加越高。") },
{ UPGRADE_INFINITE_FEVER, 1, 110, false, _T("无尽狂热"), _T("进化"), _T("狂热期间消行可延长狂热时间;连击越高,倍率越凶。") },
{ UPGRADE_SCREEN_BOMB, 1, 90, false, _T("清屏炸弹"), _T("进阶"), _T("立刻获得 1 枚炸弹;之后累计消行 16 行再获得 1 枚。按 X 清底 5 行。") },
{ UPGRADE_TERMINAL_CLEAR, 1, 108, false, _T("终末清场"), _T("进化"), _T("最后一搏启动时,自动引爆 1 枚清屏炸弹,并进入 10 秒狂热。") },
{ UPGRADE_DUAL_CHOICE, 1, 68, false, _T("双重抉择"), _T("进阶"), _T("每次升级可额外选择 1 个强化,但下一次升级所需 EXP +30%。") },
{ UPGRADE_DESTINY_WHEEL, 1, 104, false, _T("命运轮盘"), _T("进化"), _T("升级时出现 6 个选项,可选择 2 个;其中 1 个会携带诅咒。") },
{ UPGRADE_PERFECT_ROTATE, 1, 82, false, _T("完美旋转"), _T("操作"), _T("旋转受阻时,自动尝试左右各偏移 1 格完成修正。") },
{ UPGRADE_TIME_DILATION, 1, 80, false, _T("时间缓流"), _T("保命"), _T("棋盘接近顶端时自动减速,为补救留下反应空间。") },
{ UPGRADE_HIGH_PRESSURE, 1, 70, false, _T("高压悬赏"), _T("风险"), _T("下落速度提高 15%,但得分与 EXP 额外提高 50%。") },
{ UPGRADE_TETRIS_GAMBLE, 1, 78, false, _T("豪赌四消"), _T("风险"), _T("1~3 消收益降低,但四消收益暴增。") },
{ UPGRADE_EXTREME_PLAYER, 1, 114, false, _T("极限玩家"), _T("进化"), _T("融合高压悬赏与豪赌四消。速度更快,四消后短暂缓速。") },
{ UPGRADE_UPGRADE_SHOCKWAVE, 1, 106, false, _T("升级冲击波"), _T("进阶"), _T("即将升级时自动清除底部 2 行,让强化节奏更猛烈。") },
{ UPGRADE_EVOLUTION_IMPACT, 1, 118, false, _T("进化冲击"), _T("进化"), _T("升级时清除底部 3 行,并获得 10 秒双倍 EXP。") },
{ UPGRADE_CONTROL_MASTER, 1, 112, false, _T("操控大师"), _T("进化"), _T("使用备用仓后短暂降低下落速度,并额外增加 1 个预览方块。") },
{ UPGRADE_BLOCK_STORM, 1, 82, false, _T("方块风暴"), _T("爆发"), _T("接下来 5 个方块全部变为 I 块,快速制造四消机会。") },
{ UPGRADE_CROSS_PIECE, -1, 84, true, _T("十字方块"), _T("爆发"), _T("大幅提高十字方块出现率。十字方块落地后清除所在行与所在列。") },
{ UPGRADE_BLACK_HOLE, 1, 88, false, _T("黑洞奇点"), _T("特殊"), _T("获得 2 次黑洞。按 Z 吞噬棋盘上数量最多的一种固定方块。") },
{ UPGRADE_AIR_RESHAPE, 1, 82, false, _T("空中换形"), _T("操作"), _T("获得 2 次换形。按 V 将正在下落的方块重塑为 I 块。") },
{ UPGRADE_RAINBOW_PIECE, 1, 84, false, _T("彩虹方块"), _T("爆发"), _T("更高概率生成彩虹方块。落地后清除自身中心行最多的颜色,并把覆盖行染成场上最多的颜色。") },
{ UPGRADE_VOID_CORE, 1, 112, false, _T("虚空核心"), _T("进化"), _T("黑洞后额外生成 1 个彩虹方块;彩虹生效后追加一次小型黑洞。") },
{ UPGRADE_STABLE_STRUCTURE, -1, 72, true, _T("稳定结构"), _T("特殊"), _T("落地后有小概率填补邻近空洞,让阵型更加稳固。") },
{ UPGRADE_DOUBLE_GROWTH, 1, 84, false, _T("成长核心"), _T("成长"), _T("永久获得 +15% 得分与 +15% EXP;每局只能选择一次。") },
{ UPGRADE_PIECE_TUNING, -1, 64, true, _T("方块改造"), _T("特殊"), _T("固定提高 I 方块的生成概率。") },
{ UPGRADE_GAMBLER, -1, 64, true, _T("赌徒契约"), _T("特殊"), _T("选择强化时,有概率效果翻倍,也有概率本次落空。") }
};
static constexpr int kUpgradePoolSize = sizeof(kUpgradePool) / sizeof(kUpgradePool[0]);
static constexpr int kDifficultyStepMs = 30000;
static constexpr int kDifficultySpeedStepMs = 18;
static constexpr int kMaxRogueLockedRows = 4;
static constexpr int kDifficultyLevelsPerLockedRow = 3;
static constexpr int kFeverLineThreshold = 12;
static constexpr int kFeverDurationTicks = 12;
static constexpr int kScreenBombLineThreshold = 16;
static constexpr int kExplosiveBaseInterval = 6;
static constexpr int kExplosiveMinInterval = 3;
static int pendingUpgradeShockwaveRows = 0;
static bool pendingEvolutionImpactShockwave = false;
static int GetUpgradeCurrentLevel(int upgradeId);
static bool IsUpgradePrerequisiteConsumed(int upgradeId);
static int GetUpgradeBaseRarity(int upgradeId);
static int GetUpgradeDynamicWeight(const UpgradeEntry& entry);
static const TCHAR* GetPieceShortName(int pieceType);
static bool IsUpgradeSelectable(const UpgradeEntry& entry);
static int GetTopOccupiedRow();
static int GetSweeperThreshold();
static bool RollExplosivePiece();
static bool RollLaserPiece();
static bool RollCrossPiece();
static bool RollRainbowPiece();
static int TriggerBlackHole();
static int TriggerChainBlast(int lineAnchor);
static int RollNextPieceType();
static int GetRogueScoreByLines(int linesCleared);
static int GetRogueExpByLines(int linesCleared);
static int ApplyLevelProgress(PlayerStats& stats);
static int TriggerUpgradeShockwave(int rowsToClear);
static void ResolvePendingUpgradeShockwave();
static void FillUpgradeOptions();
static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount);
static void ApplyDestinyCurse();
static void ClearLockedRows();
/**
* @brief 限制 Rogue 模式的下一方块预览数量。
*/
static int GetNextPreviewLimit()
{
if (currentMode != MODE_ROGUE)
{
return 1;
}
if (rogueStats.previewCount < 1)
{
return 1;
}
if (rogueStats.previewCount > 3)
{
return 3;
}
return rogueStats.previewCount;
}
/**
* @brief 获取 Rogue 难度系统当前封锁的底部行数。
*/
int GetRogueLockedRows()
{
if (currentMode != MODE_ROGUE)
{
return 0;
}
if (rogueStats.lockedRows < 0)
{
return 0;
}
return rogueStats.lockedRows > kMaxRogueLockedRows ? kMaxRogueLockedRows : rogueStats.lockedRows;
}
/**
* @brief 计算当前模式下棋盘可操作区域的高度。
*/
int GetRoguePlayableHeight()
{
return nGameHeight - GetRogueLockedRows();
}
/**
* @brief 清空 Rogue 模式中被难度系统封锁的底部区域。
*/
static void ClearLockedRows()
{
int lockedRows = GetRogueLockedRows();
for (int y = nGameHeight - lockedRows; y < nGameHeight; y++)
{
if (y < 0 || y >= nGameHeight)
{
continue;
}
for (int x = 0; x < nGameWidth; x++)
{
workRegion[y][x] = 0;
}
}
}
/**
* @brief 根据经过时间推进 Rogue 难度、封锁行数和下落速度。
*/
void AdvanceRogueDifficulty(int elapsedMs)
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag || elapsedMs <= 0)
{
return;
}
rogueStats.difficultyElapsedMs += elapsedMs;
bool difficultyChanged = false;
while (rogueStats.difficultyElapsedMs >= kDifficultyStepMs)
{
rogueStats.difficultyElapsedMs -= kDifficultyStepMs;
rogueStats.difficultyLevel++;
difficultyChanged = true;
}
if (!difficultyChanged)
{
return;
}
int nextLockedRows = rogueStats.difficultyLevel / kDifficultyLevelsPerLockedRow;
if (nextLockedRows > kMaxRogueLockedRows)
{
nextLockedRows = kMaxRogueLockedRows;
}
bool lockedRowsChanged = nextLockedRows > rogueStats.lockedRows;
if (lockedRowsChanged)
{
rogueStats.lockedRows = nextLockedRows;
ClearLockedRows();
ComputeTarget();
}
currentFallInterval = GetRogueFallInterval();
TCHAR difficultyDetail[128];
_stprintf_s(
difficultyDetail,
_T("危险等级 Lv.%d,上方压力增强,底部封锁 %d/%d 行。"),
rogueStats.difficultyLevel,
GetRogueLockedRows(),
kMaxRogueLockedRows);
SetFeedbackMessage(lockedRowsChanged ? _T("战场收缩") : _T("压力升高"), difficultyDetail, 12);
}
/**
* @brief 读取指定强化在当前 Rogue 属性中的等级或拥有状态。
*/
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;
}
}
/**
* @brief 返回进化类强化在界面中展示的合成路线说明。
*/
const TCHAR* GetUpgradeSynthesisPath(int upgradeId)
{
switch (upgradeId)
{
case UPGRADE_CHAIN_BOMB:
return _T("\u5408\u6210\uff1a\u7206\u7834\u65b9\u5757 -> \u8fde\u73af\u70b8\u5f39");
case UPGRADE_THUNDER_LASER:
return _T("\u5408\u6210\uff1a\u96f7\u9706\u56db\u6d88 + \u6fc0\u5149\u65b9\u5757");
case UPGRADE_INFINITE_FEVER:
return _T("\u5408\u6210\uff1a\u72c2\u70ed\u6a21\u5f0f + \u66b4\u8d70\u5806\u53e0");
case UPGRADE_TERMINAL_CLEAR:
return _T("\u5408\u6210\uff1a\u6700\u540e\u4e00\u640f + \u6e05\u5c4f\u70b8\u5f39 + \u72c2\u70ed");
case UPGRADE_DESTINY_WHEEL:
return _T("\u5408\u6210\uff1a\u53cc\u91cd\u9009\u62e9 -> \u547d\u8fd0\u8f6e\u76d8");
case UPGRADE_CONTROL_MASTER:
return _T("\u5408\u6210\uff1aHold \u89e3\u9501 + \u989d\u5916\u9884\u89c8");
case UPGRADE_EXTREME_PLAYER:
return _T("\u5408\u6210\uff1a\u9ad8\u538b\u5956\u52b1 + \u8d4c\u547d\u56db\u6d88");
case UPGRADE_EVOLUTION_IMPACT:
return _T("\u5408\u6210\uff1a\u5347\u7ea7\u51b2\u51fb\u6ce2 + \u6210\u957f\u6838\u5fc3");
case UPGRADE_VOID_CORE:
return _T("\u5408\u6210\uff1a\u9ed1\u6d1e + \u5f69\u8679\u65b9\u5757");
default:
return _T("");
}
}
/**
* @brief 获取指定强化的基础稀有度分类。
*/
static int GetUpgradeBaseRarity(int upgradeId)
{
switch (upgradeId)
{
case UPGRADE_SCORE_MULTIPLIER:
case UPGRADE_EXP_MULTIPLIER:
case UPGRADE_SLOW_FALL:
case UPGRADE_COMBO_BONUS:
case UPGRADE_PRESSURE_RELIEF:
case UPGRADE_SWEEPER:
case UPGRADE_PIECE_TUNING:
return UPGRADE_RARITY_COMMON;
case UPGRADE_PREVIEW_PLUS_ONE:
case UPGRADE_LAST_CHANCE:
case UPGRADE_HOLD_UNLOCK:
case UPGRADE_EXPLOSIVE_PIECE:
case UPGRADE_CHAIN_BLAST:
case UPGRADE_LASER_PIECE:
case UPGRADE_THUNDER_TETRIS:
case UPGRADE_FEVER_MODE:
case UPGRADE_RAGE_STACK:
case UPGRADE_SCREEN_BOMB:
case UPGRADE_PERFECT_ROTATE:
case UPGRADE_TIME_DILATION:
case UPGRADE_HIGH_PRESSURE:
case UPGRADE_TETRIS_GAMBLE:
case UPGRADE_BLOCK_STORM:
case UPGRADE_CROSS_PIECE:
case UPGRADE_BLACK_HOLE:
case UPGRADE_AIR_RESHAPE:
case UPGRADE_STABLE_STRUCTURE:
case UPGRADE_DOUBLE_GROWTH:
return UPGRADE_RARITY_UNCOMMON;
case UPGRADE_RAINBOW_PIECE:
case UPGRADE_GAMBLER:
return UPGRADE_RARITY_RARE;
default:
return UPGRADE_RARITY_RARE;
}
}
/**
* @brief 判断指定强化是否已被更高阶合成强化替代。
*/
static bool IsUpgradePrerequisiteConsumed(int upgradeId)
{
switch (upgradeId)
{
case UPGRADE_EXPLOSIVE_PIECE:
return rogueStats.chainBombLevel > 0;
case UPGRADE_LASER_PIECE:
case UPGRADE_THUNDER_TETRIS:
return rogueStats.thunderLaserLevel > 0;
case UPGRADE_FEVER_MODE:
return rogueStats.infiniteFeverLevel > 0 || rogueStats.terminalClearLevel > 0;
case UPGRADE_RAGE_STACK:
return rogueStats.infiniteFeverLevel > 0;
case UPGRADE_LAST_CHANCE:
case UPGRADE_SCREEN_BOMB:
return rogueStats.terminalClearLevel > 0;
case UPGRADE_DUAL_CHOICE:
return rogueStats.destinyWheelLevel > 0;
case UPGRADE_HOLD_UNLOCK:
case UPGRADE_PREVIEW_PLUS_ONE:
return rogueStats.controlMasterLevel > 0;
case UPGRADE_HIGH_PRESSURE:
case UPGRADE_TETRIS_GAMBLE:
return rogueStats.extremePlayerLevel > 0;
case UPGRADE_UPGRADE_SHOCKWAVE:
case UPGRADE_DOUBLE_GROWTH:
return rogueStats.evolutionImpactLevel > 0;
case UPGRADE_BLACK_HOLE:
case UPGRADE_RAINBOW_PIECE:
return rogueStats.voidCoreLevel > 0;
default:
return false;
}
}
/**
* @brief 根据当前局势动态计算强化选项的抽取权重。
*/
static int GetUpgradeDynamicWeight(const UpgradeEntry& entry)
{
int weight = entry.baseWeight;
if (entry.id == UPGRADE_PRESSURE_RELIEF)
{
int topRow = GetTopOccupiedRow();
if (topRow >= 0)
{
int occupiedHeight = GetRoguePlayableHeight() - topRow;
if (occupiedHeight >= 14)
{
weight += 50;
}
else if (occupiedHeight <= 7)
{
weight -= 25;
}
}
}
if (entry.id == UPGRADE_LAST_CHANCE && rogueStats.lastChanceCount > 0)
{
weight -= 40;
}
if (entry.id == UPGRADE_SLOW_FALL && rogueStats.slowFallStacks >= 4)
{
weight -= 20;
}
if (entry.id == UPGRADE_SCREEN_BOMB && rogueStats.screenBombCount > 0)
{
weight += 20;
}
if (entry.id == UPGRADE_BLACK_HOLE && rogueStats.blackHoleCharges > 0)
{
weight += 20;
}
if (entry.id == UPGRADE_AIR_RESHAPE && rogueStats.reshapeCharges > 0)
{
weight += 20;
}
int rarity = GetUpgradeBaseRarity(entry.id);
if (rarity == UPGRADE_RARITY_UNCOMMON)
{
weight = weight * 65 / 100;
}
else if (rarity == UPGRADE_RARITY_RARE)
{
weight = weight * 40 / 100;
}
return weight < 1 ? 1 : weight;
}
/**
* @brief 把方块类型编号转换为界面展示用的短名称。
*/
static const TCHAR* GetPieceShortName(int pieceType)
{
static const TCHAR* kNames[7] =
{
_T("I"), _T("T"), _T("L"), _T("J"), _T("O"), _T("S"), _T("Z")
};
if (pieceType < 0 || pieceType >= 7)
{
return _T("?");
}
return kNames[pieceType];
}
/**
* @brief 判断强化是否满足当前等级、前置和互斥条件。
*/
static bool IsUpgradeSelectable(const UpgradeEntry& entry)
{
if (IsUpgradePrerequisiteConsumed(entry.id))
{
return false;
}
if (entry.id == UPGRADE_CHAIN_BOMB)
{
return rogueStats.explosiveLevel > 0 &&
rogueStats.chainBombLevel == 0;
}
if (entry.id == UPGRADE_THUNDER_LASER)
{
return rogueStats.thunderTetrisLevel > 0 &&
rogueStats.laserLevel > 0 &&
rogueStats.thunderLaserLevel == 0;
}
if (entry.id == UPGRADE_INFINITE_FEVER)
{
return rogueStats.feverLevel > 0 &&
rogueStats.rageStackLevel > 0 &&
rogueStats.infiniteFeverLevel == 0;
}
if (entry.id == UPGRADE_TERMINAL_CLEAR)
{
return rogueStats.lastChanceUpgradeLevel > 0 &&
rogueStats.screenBombLevel > 0 &&
rogueStats.feverLevel > 0 &&
rogueStats.terminalClearLevel == 0;
}
if (entry.id == UPGRADE_DESTINY_WHEEL)
{
return rogueStats.dualChoiceLevel > 0 &&
rogueStats.destinyWheelLevel == 0;
}
if (entry.id == UPGRADE_CONTROL_MASTER)
{
return rogueStats.holdUnlocked > 0 &&
rogueStats.previewUpgradeLevel > 0 &&
rogueStats.controlMasterLevel == 0;
}
if (entry.id == UPGRADE_EXTREME_PLAYER)
{
return rogueStats.highPressureLevel > 0 &&
rogueStats.tetrisGambleLevel > 0 &&
rogueStats.extremePlayerLevel == 0;
}
if (entry.id == UPGRADE_VOID_CORE)
{
return rogueStats.blackHoleLevel > 0 &&
rogueStats.rainbowPieceLevel > 0 &&
rogueStats.voidCoreLevel == 0;
}
if (entry.id == UPGRADE_EVOLUTION_IMPACT)
{
return rogueStats.upgradeShockwaveLevel > 0 &&
rogueStats.doubleGrowthLevel > 0 &&
rogueStats.evolutionImpactLevel == 0;
}
if (entry.repeatable)
{
return true;
}
if (entry.maxLevel <= 0)
{
return GetUpgradeCurrentLevel(entry.id) == 0;
}
return GetUpgradeCurrentLevel(entry.id) < entry.maxLevel;
}
/**
* @brief 查找棋盘中最高的已占用行,用于评估局势压力。
*/
static int GetTopOccupiedRow()
{
for (int i = 0; i < GetRoguePlayableHeight(); i++)
{
for (int j = 0; j < nGameWidth; j++)
{
if (workRegion[i][j] != 0)
{
return i;
}
}
}
return -1;
}
/**
* @brief 计算底线清道夫触发一次自动清底需要的消行充能。
*/
static int GetSweeperThreshold()
{
int reduction = (rogueStats.sweeperLevel - 1);
int threshold = 5 - reduction;
return threshold < 2 ? 2 : threshold;
}
/**
* @brief 根据爆破强化等级随机判定当前方块是否获得爆破特性。
*/
static bool RollExplosivePiece()
{
if (currentMode != MODE_ROGUE || rogueStats.explosiveLevel <= 0)
{
return false;
}
int explosiveInterval = kExplosiveBaseInterval - (rogueStats.explosiveLevel - 1);
if (explosiveInterval < kExplosiveMinInterval)
{
explosiveInterval = kExplosiveMinInterval;
}
if (rogueStats.feverTicks > 0 && explosiveInterval > kExplosiveMinInterval)
{
explosiveInterval--;
}
if (rogueStats.explosivePieceCounter < explosiveInterval)
{
rogueStats.explosivePieceCounter++;
}
if (rogueStats.explosivePieceCounter < explosiveInterval)
{
return false;
}
rogueStats.explosivePieceCounter = 0;
return true;
}
/**
* @brief 根据激光强化等级随机判定当前方块是否获得激光特性。
*/
static bool RollLaserPiece()
{
if (currentMode != MODE_ROGUE || rogueStats.laserLevel <= 0)
{
return false;
}
int chancePercent = 16 + (rogueStats.laserLevel - 1) * 10;
if (chancePercent > 55)
{
chancePercent = 55;
}
if (rogueStats.feverTicks > 0)
{
chancePercent += 15;
if (chancePercent > 70)
{
chancePercent = 70;
}
}
return (rand() % 100) < chancePercent;
}
/**
* @brief 根据十字强化等级随机判定当前方块是否获得十字清除特性。
*/
static bool RollCrossPiece()
{
if (currentMode != MODE_ROGUE || rogueStats.crossPieceLevel <= 0)
{
return false;
}
int chancePercent = 14 + (rogueStats.crossPieceLevel - 1) * 8;
if (chancePercent > 45)
{
chancePercent = 45;
}
if (rogueStats.feverTicks > 0)
{
chancePercent += 15;
if (chancePercent > 60)
{
chancePercent = 60;
}
}
return (rand() % 100) < chancePercent;
}
/**
* @brief 根据彩虹强化等级随机判定当前方块是否获得行清除与染色特性。
*/
static bool RollRainbowPiece()
{
if (currentMode != MODE_ROGUE || rogueStats.rainbowPieceLevel <= 0)
{
return false;
}
int chancePercent = 18;
if (rogueStats.feverTicks > 0)
{
chancePercent += 17;
}
return (rand() % 100) < chancePercent;
}
/**
* @brief 判断棋盘格是否属于彩虹方块固定后的特殊格。
*/
bool IsRainbowBoardCell(int cellValue)
{
return cellValue == 8;
}
/**
* @brief 触发小型黑洞,随机吞噬限定数量的固定方块。
*/
int TriggerMiniBlackHole(int maxCellsToClear)
{
int blockCounts[8] = { 0 };
for (int y = 0; y < GetRoguePlayableHeight(); 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;
Point clearedCells[16] = {};
for (int y = GetRoguePlayableHeight() - 1; y >= 0 && clearedCellCount < maxCellsToClear; y--)
{
for (int x = 0; x < nGameWidth && clearedCellCount < maxCellsToClear; x++)
{
if (workRegion[y][x] == targetBlock)
{
if (clearedCellCount < 16)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = y;
}
workRegion[y][x] = 0;
clearedCellCount++;
}
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 16 ? clearedCellCount : 16, RGB(24, 20, 28), true);
return clearedCellCount;
}
/**
* @brief 为当前活动方块刷新爆破、激光、十字和彩虹等特殊标记。
*/
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();
}
/**
* @brief 以指定棋盘格为中心清除爆破范围内的固定方块。
*/
int ClearExplosiveAreaAt(int centerY, int centerX)
{
int radius = (currentMode == MODE_ROGUE && rogueStats.chainBombLevel > 0) ? 2 : 1;
int clearedCellCount = 0;
Point clearedCells[25] = {};
for (int y = centerY - radius; y <= centerY + radius; y++)
{
for (int x = centerX - radius; x <= centerX + radius; x++)
{
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth)
{
if (workRegion[y][x] != 0)
{
if (clearedCellCount < 25)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = y;
}
clearedCellCount++;
workRegion[y][x] = 0;
}
}
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 25 ? clearedCellCount : 25, RGB(255, 116, 78), true);
return clearedCellCount;
}
/**
* @brief 清除指定列中的所有固定方块并返回清除数量。
*/
int ClearColumnAt(int column)
{
return ClearColumnAtWithColor(column, RGB(120, 232, 255));
}
/**
* @brief 使用指定颜色特效清除指定列并返回清除数量。
*/
int ClearColumnAtWithColor(int column, COLORREF flashColor)
{
int clearedCellCount = 0;
Point clearedCells[20] = {};
if (column < 0 || column >= nGameWidth)
{
return 0;
}
for (int y = 0; y < GetRoguePlayableHeight(); y++)
{
if (workRegion[y][column] != 0)
{
if (clearedCellCount < 20)
{
clearedCells[clearedCellCount].x = column;
clearedCells[clearedCellCount].y = y;
}
workRegion[y][column] = 0;
clearedCellCount++;
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, flashColor, false);
return clearedCellCount;
}
/**
* @brief 清除指定行中的所有固定方块并返回清除数量。
*/
int ClearRowAt(int row)
{
int clearedCellCount = 0;
Point clearedCells[10] = {};
if (row < 0 || row >= GetRoguePlayableHeight())
{
return 0;
}
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[row][x] != 0)
{
if (clearedCellCount < 10)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = row;
}
workRegion[row][x] = 0;
clearedCellCount++;
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, RGB(196, 255, 132), false);
return clearedCellCount;
}
/**
* @brief 触发彩虹方块行清除与覆盖行染色效果。
*/
int TriggerRainbowColorShift(int anchorRow, int minRow, int maxRow, int& recoloredCount)
{
recoloredCount = 0;
int clearedCellCount = 0;
Point clearedCells[10] = {};
Point recoloredCells[40] = {};
if (anchorRow < 0)
{
anchorRow = 0;
}
if (anchorRow >= GetRoguePlayableHeight())
{
anchorRow = GetRoguePlayableHeight() - 1;
}
if (minRow < 0)
{
minRow = 0;
}
if (maxRow >= GetRoguePlayableHeight())
{
maxRow = GetRoguePlayableHeight() - 1;
}
int rowColorCounts[8] = {};
for (int x = 0; x < nGameWidth; x++)
{
int cell = workRegion[anchorRow][x];
if (cell >= 1 && cell <= 7)
{
rowColorCounts[cell]++;
}
}
int rowTargetColor = 0;
int rowTargetCount = 0;
for (int cell = 1; cell <= 7; cell++)
{
if (rowColorCounts[cell] > rowTargetCount)
{
rowTargetColor = cell;
rowTargetCount = rowColorCounts[cell];
}
}
if (rowTargetColor > 0)
{
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[anchorRow][x] == rowTargetColor)
{
if (clearedCellCount < 10)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = anchorRow;
}
workRegion[anchorRow][x] = 0;
clearedCellCount++;
}
}
}
int boardColorCounts[8] = {};
for (int y = 0; y < GetRoguePlayableHeight(); y++)
{
for (int x = 0; x < nGameWidth; x++)
{
int cell = workRegion[y][x];
if (cell >= 1 && cell <= 7)
{
boardColorCounts[cell]++;
}
}
}
int boardTargetColor = 0;
int boardTargetCount = 0;
for (int cell = 1; cell <= 7; cell++)
{
if (boardColorCounts[cell] > boardTargetCount)
{
boardTargetColor = cell;
boardTargetCount = boardColorCounts[cell];
}
}
if (boardTargetColor == 0)
{
boardTargetColor = rowTargetColor > 0 ? rowTargetColor : 1;
}
for (int y = minRow; y <= maxRow; y++)
{
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[y][x] != 0 && workRegion[y][x] != boardTargetColor)
{
workRegion[y][x] = boardTargetColor;
if (recoloredCount < 40)
{
recoloredCells[recoloredCount].x = x;
recoloredCells[recoloredCount].y = y;
}
recoloredCount++;
}
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount, RGB(255, 255, 255), true);
TriggerColoredCellClearEffect(recoloredCells, recoloredCount < 40 ? recoloredCount : 40, RGB(218, 178, 255), false);
return clearedCellCount;
}
/**
* @brief 触发黑洞,吞噬棋盘中数量最多的一类固定方块。
*/
static int TriggerBlackHole()
{
int blockCounts[8] = { 0 };
for (int y = 0; y < GetRoguePlayableHeight(); 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;
Point clearedCells[200] = {};
for (int y = 0; y < GetRoguePlayableHeight(); y++)
{
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[y][x] == targetBlock)
{
if (clearedCellCount < 200)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = y;
}
workRegion[y][x] = 0;
clearedCellCount++;
}
}
}
TriggerColoredCellClearEffect(clearedCells, clearedCellCount < 200 ? clearedCellCount : 200, RGB(24, 20, 28), true);
return clearedCellCount;
}
/**
* @brief 引爆清屏炸弹,清除当前可玩区域底部多行方块。
*/
int TriggerScreenBomb()
{
int clearedCellCount = 0;
Point clearedCells[50] = {};
for (int i = 0; i < 5; i++)
{
int row = GetRoguePlayableHeight() - 1 - i;
if (row < 0)
{
break;
}
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[row][x] != 0)
{
if (clearedCellCount < 50)
{
clearedCells[clearedCellCount].x = x;
clearedCells[clearedCellCount].y = row;
}
clearedCellCount++;
}
}
DeleteOneLine(row);
}
TriggerCellClearEffect(clearedCells, clearedCellCount < 50 ? clearedCellCount : 50, true);
return clearedCellCount;
}
/**
* @brief 围绕消行位置触发连锁火花,随机破坏附近固定方块。
*/
static int TriggerChainBlast(int lineAnchor)
{
if (currentMode != MODE_ROGUE || rogueStats.chainBlastLevel <= 0)
{
return 0;
}
int clearedCellCount = 0;
Point clearedCells[16] = {};
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 >= GetRoguePlayableHeight())
{
randomY = GetRoguePlayableHeight() - 1;
}
if (workRegion[randomY][randomX] != 0)
{
if (clearedCellCount < 16)
{
clearedCells[clearedCellCount].x = randomX;
clearedCells[clearedCellCount].y = randomY;
}
workRegion[randomY][randomX] = 0;
clearedCellCount++;
}
}
TriggerCellClearEffect(clearedCells, clearedCellCount < 16 ? clearedCellCount : 16, false);
return clearedCellCount;
}
/**
* @brief 根据 Rogue 强化和权重系统随机生成下一个方块类型。
*/
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)
{
weights[0] += rogueStats.pieceTuningLevels[0] * 55;
if (weights[0] > 430)
{
weights[0] = 430;
}
}
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;
}
/**
* @brief 尝试用稳定结构强化填补局部空洞并返回填补数量。
*/
int TryStabilizeBoard()
{
if (currentMode != MODE_ROGUE || rogueStats.stableStructureLevel <= 0)
{
return 0;
}
int triggerChance = 25 + (rogueStats.stableStructureLevel - 1) * 12;
if (triggerChance > 70)
{
triggerChance = 70;
}
if ((rand() % 100) >= triggerChance)
{
return 0;
}
for (int y = GetRoguePlayableHeight() - 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;
}
/**
* @brief 重置下一方块队列并按当前预览上限填充。
*/
void ResetNextQueue()
{
for (int i = 0; i < 3; i++)
{
nextTypes[i] = RollNextPieceType();
}
}
/**
* @brief 取出队首方块并补充新的下一方块。
*/
int ConsumeNextType()
{
int nextType = nextTypes[0];
nextTypes[0] = nextTypes[1];
nextTypes[1] = nextTypes[2];
nextTypes[2] = RollNextPieceType();
return nextType;
}
/**
* @brief 按 Rogue 规则计算指定消行数对应的基础得分。
*/
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;
}
}
/**
* @brief 按 Rogue 规则计算指定消行数对应的基础经验。
*/
static int GetRogueExpByLines(int linesCleared)
{
switch (linesCleared)
{
case 1: return 12;
case 2: return 32;
case 3: return 58;
case 4: return 100;
default: return 0;
}
}
/**
* @brief 结算经验条并返回本次连续升级次数。
*/
static int ApplyLevelProgress(PlayerStats& stats)
{
int levelUps = 0;
while (stats.requiredExp > 0 && stats.exp >= stats.requiredExp)
{
stats.exp -= stats.requiredExp;
stats.level++;
stats.requiredExp = 6 + stats.level * 5;
levelUps++;
}
return levelUps;
}
/**
* @brief 触发升级冲击波,清除底部指定数量的行。
*/
static int TriggerUpgradeShockwave(int rowsToClear)
{
int clearedRows = 0;
for (int i = 0; i < rowsToClear; i++)
{
DeleteOneLine(GetRoguePlayableHeight() - 1);
clearedRows++;
}
return clearedRows;
}
/**
* @brief 按权重和当前局势生成升级菜单中的候选强化。
*/
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) ? 6 : 3;
int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit;
upgradeUiState.optionCount = optionCount;
upgradeUiState.selectedIndex = 0;
upgradeUiState.picksRemaining = (rogueStats.dualChoiceLevel > 0 || rogueStats.destinyWheelLevel > 0) ? 2 : 1;
upgradeUiState.markedCount = 0;
for (int i = 0; i < 6; i++)
{
upgradeUiState.marked[i] = false;
}
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].rarity = GetUpgradeBaseRarity(pickedEntry.id);
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 = 0;
upgradeUiState.options[i].targetPieceType = targetPieceType;
upgradeUiState.options[i].currentLevel = rogueStats.pieceTuningLevels[0];
upgradeUiState.options[i].name = _T("方块改造");
static TCHAR tuningDescriptions[6][64];
_stprintf_s(
tuningDescriptions[i],
_T("%s 块的生成概率提高。"),
GetPieceShortName(0));
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;
}
}
/**
* @brief 综合难度、强化和临时状态计算 Rogue 模式下落间隔。
*/
int GetRogueFallInterval()
{
int baseInterval = 500 + rogueStats.slowFallStacks * 80;
baseInterval -= rogueStats.difficultyLevel * kDifficultySpeedStepMs;
if (rogueStats.highPressureLevel > 0)
{
baseInterval = baseInterval * 85 / 100;
}
if (rogueStats.extremePlayerLevel > 0)
{
baseInterval = baseInterval * 90 / 100;
}
if (rogueStats.extremeDangerLevel > 0)
{
baseInterval = baseInterval * (100 - rogueStats.extremeDangerLevel * 8) / 100;
}
if (currentMode == MODE_ROGUE && rogueStats.feverTicks > 0)
{
baseInterval += 120;
}
if (rogueStats.timeDilationTicks > 0)
{
baseInterval = baseInterval * 130 / 100;
}
if (rogueStats.extremeSlowTicks > 0)
{
baseInterval += 180;
}
if (rogueStats.holdSlowTicks > 0)
{
baseInterval += 140;
}
if (baseInterval < 120)
{
baseInterval = 120;
}
return baseInterval;
}
/**
* @brief 根据强化编号把对应效果写入 Rogue 属性。
*/
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;
rogueStats.screenBombCount += applyCount;
break;
case UPGRADE_TERMINAL_CLEAR:
rogueStats.terminalClearLevel = 1;
break;
case UPGRADE_DUAL_CHOICE:
rogueStats.dualChoiceLevel = 1;
rogueStats.requiredExp = rogueStats.requiredExp * 130 / 100;
if (rogueStats.requiredExp < 10)
{
rogueStats.requiredExp = 10;
}
break;
case UPGRADE_DESTINY_WHEEL:
rogueStats.destinyWheelLevel = 1;
break;
case UPGRADE_PERFECT_ROTATE:
rogueStats.perfectRotateLevel = 1;
break;
case UPGRADE_TIME_DILATION:
rogueStats.timeDilationLevel = 1;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_HIGH_PRESSURE:
rogueStats.highPressureLevel = 1;
rogueStats.scoreMultiplierPercent += 50;
rogueStats.expMultiplierPercent += 50;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_TETRIS_GAMBLE:
rogueStats.tetrisGambleLevel = 1;
break;
case UPGRADE_EXTREME_PLAYER:
rogueStats.extremePlayerLevel = 1;
rogueStats.extremeDangerTicks = 30;
rogueStats.extremeDangerLevel = 0;
currentFallInterval = GetRogueFallInterval();
break;
case UPGRADE_UPGRADE_SHOCKWAVE:
rogueStats.upgradeShockwaveLevel = 1;
break;
case UPGRADE_EVOLUTION_IMPACT:
rogueStats.evolutionImpactLevel = 1;
break;
case UPGRADE_CONTROL_MASTER:
rogueStats.controlMasterLevel = 1;
if (rogueStats.previewCount < 3)
{
rogueStats.previewCount++;
}
rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1;
break;
case UPGRADE_BLOCK_STORM:
rogueStats.blockStormLevel = 1;
rogueStats.blockStormPiecesRemaining = 5;
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 += 2 * applyCount;
break;
case UPGRADE_AIR_RESHAPE:
rogueStats.reshapeLevel = 1;
rogueStats.reshapeCharges += 2 * applyCount;
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:
rogueStats.pieceTuningLevels[0] += applyCount;
break;
case UPGRADE_GAMBLER:
rogueStats.gamblerLevel += applyCount;
break;
default:
break;
}
}
/**
* @brief 为技能清除的格子结算得分和经验奖励。
*/
void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain, bool allowLevelProgress)
{
scoreGain = 0;
expGain = 0;
if (currentMode != MODE_ROGUE || clearedCells <= 0)
{
return;
}
scoreGain = clearedCells * rogueStats.scoreMultiplierPercent / 100;
expGain = clearedCells * rogueStats.expMultiplierPercent / 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;
}
if (scoreGain < clearedCells)
{
scoreGain = clearedCells;
}
if (expGain < clearedCells)
{
expGain = clearedCells;
}
rogueStats.score += scoreGain;
rogueStats.exp += expGain;
tScore = rogueStats.score;
if (rogueStats.screenBombLevel > 0)
{
rogueStats.screenBombCharge += clearedCells;
while (rogueStats.screenBombCharge >= kScreenBombLineThreshold)
{
rogueStats.screenBombCharge -= kScreenBombLineThreshold;
rogueStats.screenBombCount++;
}
}
if (allowLevelProgress)
{
int levelUps = ApplyLevelProgress(rogueStats);
if (levelUps > 0)
{
upgradeUiState.pendingCount += levelUps;
OpenUpgradeMenu();
}
}
}
/**
* @brief 检查 Rogue 经验是否升级,并在需要时打开强化选择界面。
*/
void CheckRogueLevelProgress()
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING)
{
return;
}
int levelUps = ApplyLevelProgress(rogueStats);
if (levelUps <= 0)
{
return;
}
upgradeUiState.pendingCount += levelUps;
int shockwaveRows = 0;
if (rogueStats.evolutionImpactLevel > 0)
{
shockwaveRows = 3;
rogueStats.feverTicks = kFeverDurationTicks;
currentFallInterval = GetRogueFallInterval();
}
else if (rogueStats.upgradeShockwaveLevel > 0)
{
shockwaveRows = 2;
}
if (shockwaveRows > 0)
{
pendingUpgradeShockwaveRows = shockwaveRows;
pendingEvolutionImpactShockwave = rogueStats.evolutionImpactLevel > 0;
}
TCHAR feedbackTitle[64];
TCHAR feedbackDetail[128];
_stprintf_s(feedbackTitle, _T("灵感涌现 x%d"), levelUps);
_stprintf_s(
feedbackDetail,
_T("等级 Lv.%d EXP %d/%d 选择新的强化"),
rogueStats.level,
rogueStats.exp,
rogueStats.requiredExp);
SetFeedbackMessage(feedbackTitle, feedbackDetail, 10);
OpenUpgradeMenu();
}
/**
* @brief 对棋盘固定方块应用重力,使悬空方块自然下落。
*/
void ApplyBoardGravity()
{
int playableHeight = GetRoguePlayableHeight();
for (int x = 0; x < nGameWidth; x++)
{
int writeY = playableHeight - 1;
for (int y = playableHeight - 1; y >= 0; y--)
{
if (workRegion[y][x] != 0)
{
int cell = workRegion[y][x];
workRegion[y][x] = 0;
workRegion[writeY][x] = cell;
writeY--;
}
}
for (int y = writeY; y >= 0; y--)
{
workRegion[y][x] = 0;
}
}
}
/**
* @brief 结算一次标准消行带来的 Rogue 得分、经验、连击和派生效果。
*/
void ApplyLineClearResult(int linesCleared)
{
if (linesCleared <= 0)
{
if (currentMode == MODE_ROGUE)
{
rogueStats.comboChain = 0;
}
return;
}
if (currentMode == MODE_CLASSIC)
{
classicStats.totalLinesCleared += linesCleared;
classicStats.score += linesCleared * 100;
tScore = classicStats.score;
return;
}
int scoreGain = GetRogueScoreByLines(linesCleared);
scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100;
int expGain = GetRogueExpByLines(linesCleared);
expGain = expGain * rogueStats.expMultiplierPercent / 100;
if (rogueStats.tetrisGambleLevel > 0)
{
if (linesCleared == 4)
{
scoreGain *= 4;
}
else
{
scoreGain = scoreGain * 50 / 100;
}
}
if (rogueStats.doubleGrowthLevel > 0)
{
int growthMultiplierPercent = 100 + rogueStats.doubleGrowthLevel * 15;
scoreGain = scoreGain * growthMultiplierPercent / 100;
expGain = expGain * growthMultiplierPercent / 100;
}
if (rogueStats.feverTicks > 0)
{
scoreGain *= 2;
expGain *= 2;
}
int gamblerBonusPercent = 0;
if (rogueStats.gamblerLevel > 0)
{
int variance = 20 + (rogueStats.gamblerLevel - 1) * 10;
if (variance > 50)
{
variance = 50;
}
gamblerBonusPercent = (rand() % (variance * 2 + 1)) - variance;
scoreGain = scoreGain * (100 + gamblerBonusPercent) / 100;
expGain = expGain * (100 + gamblerBonusPercent) / 100;
}
rogueStats.comboChain++;
if (rogueStats.comboBonusStacks > 0 && rogueStats.comboChain > 1)
{
scoreGain += (rogueStats.comboChain - 1) * rogueStats.comboBonusStacks * 50;
}
if (rogueStats.rageStackLevel > 0 && rogueStats.comboChain > 1)
{
int rageBonusPercent = (rogueStats.comboChain - 1) * 15;
if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0)
{
rageBonusPercent += (rogueStats.comboChain - 1) * 10;
}
scoreGain += scoreGain * rageBonusPercent / 100;
}
rogueStats.totalLinesCleared += linesCleared;
rogueStats.score += scoreGain;
rogueStats.exp += expGain;
if (rogueStats.feverLevel > 0)
{
rogueStats.feverLineCharge += linesCleared;
while (rogueStats.feverLineCharge >= kFeverLineThreshold)
{
rogueStats.feverLineCharge -= kFeverLineThreshold;
rogueStats.feverTicks = kFeverDurationTicks;
currentFallInterval = GetRogueFallInterval();
SetFeedbackMessage(_T("\u8fdb\u5165\u72c2\u70ed\u6a21\u5f0f"), _T("\u672a\u6765 12 \u79d2\u5f97\u5206 / EXP x2\uff0c\u4e0b\u843d\u66f4\u6162\uff0c\u7279\u6b8a\u65b9\u5757\u66f4\u6613\u51fa\u73b0\u3002"), 12);
}
}
if (rogueStats.infiniteFeverLevel > 0 && rogueStats.feverTicks > 0)
{
rogueStats.feverTicks += 1;
if (linesCleared == 4)
{
rogueStats.feverTicks += 3;
}
if (rogueStats.feverTicks > 24)
{
rogueStats.feverTicks = 24;
}
currentFallInterval = GetRogueFallInterval();
}
if (rogueStats.extremePlayerLevel > 0 && linesCleared == 4)
{
rogueStats.extremeSlowTicks = 5;
rogueStats.extremeDangerTicks = 30;
currentFallInterval = GetRogueFallInterval();
SetFeedbackMessage(_T("\u6781\u9650\u73a9\u5bb6\u89e6\u53d1"), _T("\u56db\u6d88\u6210\u529f\uff0c\u63a5\u4e0b\u6765 5 \u79d2\u77ed\u6682\u7f13\u901f\u8865\u4f4d\u3002"), 12);
}
if (rogueStats.screenBombLevel > 0)
{
rogueStats.screenBombCharge += linesCleared;
while (rogueStats.screenBombCharge >= kScreenBombLineThreshold)
{
rogueStats.screenBombCharge -= kScreenBombLineThreshold;
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(GetRoguePlayableHeight() - 1 - i);
}
if (chainBlastCells > 0)
{
int chainBlastScore = 0;
int chainBlastExp = 0;
AwardRogueSkillClearRewards(chainBlastCells, chainBlastScore, chainBlastExp, false);
ApplyBoardGravity();
TCHAR blastDetail[128];
_stprintf_s(
blastDetail,
_T("余波炸裂,清除 %d 格 +%d 分 +%d EXP"),
chainBlastCells,
chainBlastScore,
chainBlastExp);
SetFeedbackMessage(_T("连锁火花"), blastDetail, 12);
}
}
if (linesCleared >= 3 && rogueStats.thunderTetrisLevel > 0)
{
int thunderRowsCleared = 0;
Point thunderCells[20] = {};
for (int i = 0; i < 2; i++)
{
int randomRow = rand() % GetRoguePlayableHeight();
for (int x = 0; x < nGameWidth; x++)
{
if (workRegion[randomRow][x] != 0)
{
if (thunderRowsCleared < 20)
{
thunderCells[thunderRowsCleared].x = x;
thunderCells[thunderRowsCleared].y = randomRow;
}
workRegion[randomRow][x] = 0;
thunderRowsCleared++;
}
}
}
if (thunderRowsCleared > 0)
{
TriggerCellClearEffect(thunderCells, thunderRowsCleared < 20 ? thunderRowsCleared : 20, true);
int thunderScore = 0;
int thunderExp = 0;
AwardRogueSkillClearRewards(thunderRowsCleared, thunderScore, thunderExp, false);
ApplyBoardGravity();
SetFeedbackMessage(_T("雷霆四消"), _T("雷击落下,额外清理了 2 行范围内的方块。"), 12);
}
}
if (linesCleared >= 3 && rogueStats.thunderLaserLevel > 0)
{
int laserCellsCleared = 0;
for (int i = 0; i < 2; i++)
{
laserCellsCleared += ClearColumnAt(rand() % nGameWidth);
}
if (laserCellsCleared > 0)
{
int laserScore = 0;
int laserExp = 0;
AwardRogueSkillClearRewards(laserCellsCleared, laserScore, laserExp, false);
ApplyBoardGravity();
TCHAR thunderLaserDetail[128];
_stprintf_s(
thunderLaserDetail,
_T("雷光穿透,清除 %d 格 +%d 分 +%d EXP"),
laserCellsCleared,
laserScore,
laserExp);
SetFeedbackMessage(_T("雷霆棱镜"), thunderLaserDetail, 12);
}
}
if (rogueStats.sweeperLevel > 0)
{
rogueStats.sweeperCharge += linesCleared;
int sweeperThreshold = GetSweeperThreshold();
int clearedBySweeper = 0;
while (rogueStats.sweeperCharge >= sweeperThreshold)
{
rogueStats.sweeperCharge -= sweeperThreshold;
DeleteOneLine(GetRoguePlayableHeight() - 1);
clearedBySweeper++;
}
if (clearedBySweeper > 0)
{
TCHAR sweeperDetail[128];
_stprintf_s(
sweeperDetail,
_T("清扫底部 %d 行 充能 %d/%d"),
clearedBySweeper,
rogueStats.sweeperCharge,
sweeperThreshold);
SetFeedbackMessage(_T("底线清道夫"), 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 = kFeverDurationTicks;
currentFallInterval = GetRogueFallInterval();
}
else if (rogueStats.upgradeShockwaveLevel > 0)
{
shockwaveRows = 2;
}
if (shockwaveRows > 0)
{
pendingUpgradeShockwaveRows = shockwaveRows;
pendingEvolutionImpactShockwave = rogueStats.evolutionImpactLevel > 0;
}
TCHAR feedbackTitle[64];
TCHAR feedbackDetail[128];
_stprintf_s(feedbackTitle, _T("灵感涌现 x%d"), levelUps);
_stprintf_s(
feedbackDetail,
_T("等级 Lv.%d EXP %d/%d 选择新的强化"),
rogueStats.level,
rogueStats.exp,
rogueStats.requiredExp);
SetFeedbackMessage(feedbackTitle, feedbackDetail, 10);
OpenUpgradeMenu();
}
currentFallInterval = GetRogueFallInterval();
}
/**
* @brief 应用命运轮盘的诅咒,提高下一次升级所需经验。
*/
static void ApplyDestinyCurse()
{
rogueStats.requiredExp = rogueStats.requiredExp * 125 / 100;
if (rogueStats.requiredExp < 10)
{
rogueStats.requiredExp = 10;
}
}
/**
* @brief 在升级选择结束后处理延迟触发的升级冲击波。
*/
static void ResolvePendingUpgradeShockwave()
{
if (pendingUpgradeShockwaveRows <= 0)
{
return;
}
int shockwaveRows = pendingUpgradeShockwaveRows;
bool evolutionImpact = pendingEvolutionImpactShockwave;
pendingUpgradeShockwaveRows = 0;
pendingEvolutionImpactShockwave = false;
int clearedRows = TriggerUpgradeShockwave(shockwaveRows);
int effectRows[4] = {};
int effectRowCount = clearedRows;
if (effectRowCount > 4)
{
effectRowCount = 4;
}
for (int i = 0; i < effectRowCount; i++)
{
effectRows[i] = GetRoguePlayableHeight() - 1 - i;
}
TriggerLineClearEffect(effectRows, effectRowCount, clearedRows);
int scoreGain = 0;
int expGain = 0;
AwardRogueSkillClearRewards(clearedRows * nGameWidth, scoreGain, expGain, true);
TCHAR shockwaveDetail[128];
if (evolutionImpact)
{
_stprintf_s(
shockwaveDetail,
_T("进化能量爆发,清除底部 %d 行 +%d 分 +%d EXP。"),
clearedRows,
scoreGain,
expGain);
SetFeedbackMessage(_T("进化冲击"), shockwaveDetail, 14);
}
else
{
_stprintf_s(shockwaveDetail, _T("灵感涌现后,冲击波清除底部 %d 行 +%d 分 +%d EXP。"), clearedRows, scoreGain, expGain);
SetFeedbackMessage(_T("升级冲击波"), shockwaveDetail, 12);
}
}
/**
* @brief 重置 Rogue 模式中等待播放的特殊视觉事件。
*/
void ResetPendingRogueVisualEvents()
{
pendingUpgradeShockwaveRows = 0;
pendingEvolutionImpactShockwave = false;
}
/**
* @brief 在 Rogue 模式中打开升级强化选择界面。
*/
void OpenUpgradeMenu()
{
if (currentMode != MODE_ROGUE || upgradeUiState.pendingCount <= 0)
{
return;
}
FillUpgradeOptions();
currentScreen = SCREEN_UPGRADE;
}
/**
* @brief 确认升级菜单中的选择并应用对应强化效果。
*/
void ConfirmUpgradeSelection()
{
if (currentScreen != SCREEN_UPGRADE || upgradeUiState.optionCount <= 0)
{
return;
}
if (upgradeUiState.picksRemaining > 1)
{
if (upgradeUiState.markedCount != upgradeUiState.picksRemaining)
{
SetFeedbackMessage(_T("选择未完成"), _T("请先标记足够的强化,再按 Enter 确认。"), 10);
return;
}
TCHAR feedbackTitle[64] = _T("获得强化");
TCHAR feedbackDetail[128] = _T("");
int appliedSelections = 0;
for (int optionIndex = 0; optionIndex < upgradeUiState.optionCount; optionIndex++)
{
if (!upgradeUiState.marked[optionIndex])
{
continue;
}
UpgradeOption selectedOption = upgradeUiState.options[optionIndex];
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("(翻倍)"));
}
else if (roll >= 100 - gamblerChance)
{
applyCount = 0;
_stprintf_s(gamblerSuffix, _T("(落空)"));
}
}
ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount);
upgradeUiState.totalChosenCount++;
if (selectedOption.cursed)
{
ApplyDestinyCurse();
}
if (appliedSelections == 0)
{
_stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name);
}
else
{
_stprintf_s(feedbackTitle, _T("获得强化 x%d"), appliedSelections + 1);
}
if (lstrlen(feedbackDetail) > 0)
{
_stprintf_s(feedbackDetail + lstrlen(feedbackDetail), 128 - lstrlen(feedbackDetail), _T(""));
}
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
128 - lstrlen(feedbackDetail),
_T("%s%s%s"),
selectedOption.name,
gamblerSuffix,
selectedOption.cursed ? _T(" [诅咒]") : _T(""));
appliedSelections++;
}
SetFeedbackMessage(feedbackTitle, feedbackDetail, 12);
if (upgradeUiState.pendingCount > 0)
{
upgradeUiState.pendingCount--;
}
if (upgradeUiState.pendingCount > 0)
{
FillUpgradeOptions();
currentScreen = SCREEN_UPGRADE;
return;
}
upgradeUiState.optionCount = 0;
upgradeUiState.picksRemaining = 0;
upgradeUiState.markedCount = 0;
currentScreen = SCREEN_PLAYING;
ResolvePendingUpgradeShockwave();
PlayPendingLineClearEffect();
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(" 赌徒命中:效果翻倍"));
}
else if (roll >= 100 - gamblerChance)
{
applyCount = 0;
_stprintf_s(gamblerSuffix, _T(" 赌徒失手:本次落空"));
}
}
ApplyUpgradeById(selectedOption.id, selectedOption.targetPieceType, applyCount);
upgradeUiState.totalChosenCount++;
TCHAR feedbackTitle[64];
TCHAR feedbackDetail[128];
_stprintf_s(feedbackTitle, _T("获得强化:%s"), selectedOption.name);
if (selectedOption.id == UPGRADE_PIECE_TUNING && selectedOption.targetPieceType >= 0)
{
_stprintf_s(
feedbackDetail,
_T("%s 块的生成概率提高%s"),
GetPieceShortName(0),
gamblerSuffix);
}
else
{
_stprintf_s(feedbackDetail, _T("%s%s"), selectedOption.description, gamblerSuffix);
}
if (selectedOption.cursed)
{
ApplyDestinyCurse();
_stprintf_s(
feedbackDetail + lstrlen(feedbackDetail),
128 - lstrlen(feedbackDetail),
_T(" 诅咒缠身:下一次升级所需 EXP 提高 25%。"));
}
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;
upgradeUiState.markedCount = 0;
currentScreen = SCREEN_PLAYING;
ResolvePendingUpgradeShockwave();
PlayPendingLineClearEffect();
}
/**
* @brief 执行 Hold 操作,在备用仓与当前方块之间交换。
*/
void HoldCurrentPiece()
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
{
return;
}
if (rogueStats.holdUnlocked == 0)
{
SetFeedbackMessage(_T("备用仓未开启"), _T("获得备用仓强化后,才能用 C 或 Shift 暂存方块。"), 10);
return;
}
if (holdUsedThisTurn)
{
SetFeedbackMessage(_T("备用仓冷却中"), _T("每个方块落地前只能暂存一次。"), 10);
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("交换失败"), _T("换出的方块无处落位,战局崩塌。"), 12);
return;
}
ComputeTarget();
if (previousHoldType < 0)
{
SetFeedbackMessage(_T("已存入备用仓"), _T("当前方块已暂存,新的方块进入战场。"), 10);
}
else
{
SetFeedbackMessage(_T("备用仓交换"), _T("已换出备用方块。"), 10);
}
}
/**
* @brief 响应玩家主动使用清屏炸弹的操作。
*/
void UseScreenBomb()
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
{
return;
}
if (rogueStats.screenBombLevel <= 0)
{
SetFeedbackMessage(_T("清屏炸弹未获得"), _T("掌握清屏炸弹后,才能按 X 主动引爆。"), 10);
return;
}
if (rogueStats.screenBombCount <= 0)
{
SetFeedbackMessage(_T("清屏炸弹未就绪"), _T("继续消行充能,充满后即可使用。"), 10);
return;
}
rogueStats.screenBombCount--;
int clearedCells = TriggerScreenBomb();
ApplyBoardGravity();
int scoreGain = 0;
int expGain = 0;
AwardRogueSkillClearRewards(clearedCells, scoreGain, expGain, true);
TCHAR detail[128];
_stprintf_s(
detail,
_T("炸开底部 5 行,清除 %d 格 +%d 分 +%d EXP"),
clearedCells,
scoreGain,
expGain);
SetFeedbackMessage(_T("清屏炸弹引爆"), detail, 12);
currentFallInterval = GetRogueFallInterval();
ComputeTarget();
}
/**
* @brief 响应玩家主动释放黑洞的操作。
*/
void UseBlackHole()
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
{
return;
}
if (rogueStats.blackHoleLevel <= 0)
{
SetFeedbackMessage(_T("黑洞尚未觉醒"), _T("获得黑洞奇点后,才能按 Z 释放。"), 10);
return;
}
if (rogueStats.blackHoleCharges <= 0)
{
SetFeedbackMessage(_T("黑洞能量不足"), _T("没有可释放的黑洞次数。"), 10);
return;
}
int clearedCells = TriggerBlackHole();
if (clearedCells <= 0)
{
SetFeedbackMessage(_T("黑洞无处落点"), _T("棋盘上没有可吞噬的固定方块。"), 10);
return;
}
rogueStats.blackHoleCharges--;
ApplyBoardGravity();
int scoreGain = 0;
int expGain = 0;
AwardRogueSkillClearRewards(clearedCells, scoreGain, expGain, true);
if (rogueStats.voidCoreLevel > 0)
{
rogueStats.pendingRainbowPieceCount++;
}
TCHAR detail[128];
if (rogueStats.voidCoreLevel > 0)
{
_stprintf_s(
detail,
_T("吞噬最多的一种方块,清除 %d 格 +%d 分 +%d EXP 并召来 1 个彩虹方块"),
clearedCells,
scoreGain,
expGain);
}
else
{
_stprintf_s(
detail,
_T("吞噬最多的一种方块,清除 %d 格 +%d 分 +%d EXP"),
clearedCells,
scoreGain,
expGain);
}
SetFeedbackMessage(_T("黑洞展开"), detail, 12);
currentFallInterval = GetRogueFallInterval();
ComputeTarget();
}
/**
* @brief 响应玩家主动使用空中换形,将当前方块重塑为 I 块。
*/
void UseAirReshape()
{
if (currentMode != MODE_ROGUE || currentScreen != SCREEN_PLAYING || suspendFlag || gameOverFlag)
{
return;
}
if (rogueStats.reshapeLevel <= 0)
{
SetFeedbackMessage(_T("换形术未掌握"), _T("获得空中换形后,才能按 V 重塑方块。"), 10);
return;
}
if (rogueStats.reshapeCharges <= 0)
{
SetFeedbackMessage(_T("换形能量不足"), _T("没有可使用的换形次数。"), 10);
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();
}