324 lines
9.7 KiB
C++
324 lines
9.7 KiB
C++
#include "stdafx.h"
|
|
/**
|
|
* @file TetrisCoreHelpers.cpp
|
|
* @brief 存放基础逻辑框架函数之外的内部辅助流程。
|
|
*/
|
|
|
|
#include "Tetris.h"
|
|
#include "TetrisLogicInternal.h"
|
|
|
|
/**
|
|
* @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 计算得到的生成坐标。
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。
|
|
* @param overflowTop 返回是否有方块格位于可视区域顶部之外。
|
|
* @param fixedCells 返回普通落地格,用于后续特殊效果定位。
|
|
* @param fixedCellCount 返回普通落地格数量。
|
|
* @param explosiveCells 返回爆破方块落地格。
|
|
* @param explosiveCellCount 返回爆破方块落地格数量。
|
|
*/
|
|
void CollectAndWriteFixedCells(
|
|
bool& overflowTop,
|
|
Point fixedCells[],
|
|
int& fixedCellCount,
|
|
Point explosiveCells[],
|
|
int& explosiveCellCount)
|
|
{
|
|
overflowTop = false;
|
|
fixedCellCount = 0;
|
|
explosiveCellCount = 0;
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
{
|
|
for (int j = 0; j < 4; j++)
|
|
{
|
|
if (bricks[type][state][i][j] == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int fixY = point.y + i;
|
|
int fixX = point.x + j;
|
|
|
|
// 顶部溢出只记录状态,真正的复活或结束逻辑在后续统一处理。
|
|
if (fixY < 0)
|
|
{
|
|
overflowTop = true;
|
|
}
|
|
|
|
if (fixY >= 0 && fixY < GetRoguePlayableHeight() && fixX >= 0 && fixX < nGameWidth)
|
|
{
|
|
workRegion[fixY][fixX] = currentPieceIsRainbow ? 8 : bricks[type][state][i][j];
|
|
if (fixedCellCount < 4)
|
|
{
|
|
fixedCells[fixedCellCount].x = fixX;
|
|
fixedCells[fixedCellCount].y = fixY;
|
|
fixedCellCount++;
|
|
}
|
|
if (currentPieceIsExplosive && explosiveCellCount < 4)
|
|
{
|
|
explosiveCells[explosiveCellCount].x = fixX;
|
|
explosiveCells[explosiveCellCount].y = fixY;
|
|
explosiveCellCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。
|
|
* @param overflowTop 是否出现顶部溢出。
|
|
* @return 溢出已被处理且游戏可以继续时返回 true;需要结束游戏时返回 false。
|
|
*/
|
|
bool ResolveFixingOverflow(bool overflowTop)
|
|
{
|
|
if (!overflowTop)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// 终末清场优先级高于普通最后一搏,会消耗一次最后一搏和一枚清屏炸弹。
|
|
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("终末清场启动,清除 %d 格,并进入 10 秒狂热。"),
|
|
clearedByTerminal);
|
|
SetFeedbackMessage(_T("终末清场"), terminalDetail, 14);
|
|
return true;
|
|
}
|
|
|
|
// 最后一搏只清理底部三行,让顶部溢出的局面获得一次继续机会。
|
|
if (currentMode == MODE_ROGUE && rogueStats.lastChanceCount > 0)
|
|
{
|
|
rogueStats.lastChanceCount--;
|
|
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
DeleteOneLine(GetRoguePlayableHeight() - 1);
|
|
}
|
|
|
|
SetFeedbackMessage(
|
|
_T("最后一搏"),
|
|
_T("底部 3 行被清除,战局得以延续。"),
|
|
14);
|
|
return true;
|
|
}
|
|
|
|
gameOverFlag = true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。
|
|
*/
|
|
void SpawnNextFallingPiece()
|
|
{
|
|
// 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。
|
|
type = ConsumeNextType();
|
|
nType = nextTypes[0];
|
|
state = 0;
|
|
holdUsedThisTurn = false;
|
|
RollCurrentPieceSpecialFlags(true);
|
|
point = GetSpawnPoint(type);
|
|
target = point;
|
|
ComputeTarget();
|
|
}
|
|
|
|
/**
|
|
* @brief 从底向上扫描满行并删除,记录本次消除的原始行号。
|
|
* @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。
|
|
* @param clearedRowCount 返回记录的行号数量。
|
|
* @return 本次标准消行数量。
|
|
*/
|
|
int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount)
|
|
{
|
|
int clearedLines = 0;
|
|
clearedRowCount = 0;
|
|
|
|
// 从底向上扫描,删除后 i++ 让当前位置继续检查新落下来的行。
|
|
int playableHeight = GetRoguePlayableHeight();
|
|
for (int i = playableHeight - 1; i >= 0; i--)
|
|
{
|
|
bool fullLine = true;
|
|
|
|
for (int j = 0; j < nGameWidth; j++)
|
|
{
|
|
if (workRegion[i][j] == 0)
|
|
{
|
|
fullLine = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fullLine)
|
|
{
|
|
if (clearedRowCount < 8)
|
|
{
|
|
clearedRows[clearedRowCount] = i;
|
|
clearedRowCount++;
|
|
}
|
|
|
|
DeleteOneLine(i);
|
|
clearedLines++;
|
|
i++;
|
|
}
|
|
}
|
|
|
|
return clearedLines;
|
|
}
|
|
|
|
/**
|
|
* @brief 根据当前界面状态立即播放或暂存消行动画。
|
|
* @param clearedRows 已消除行号数组。
|
|
* @param clearedRowCount 行号数量。
|
|
* @param clearedLines 本次消行数量。
|
|
*/
|
|
void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines)
|
|
{
|
|
if (currentScreen == SCREEN_UPGRADE)
|
|
{
|
|
QueueLineClearEffect(clearedRows, clearedRowCount, clearedLines);
|
|
}
|
|
else
|
|
{
|
|
TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 处理连环炸弹因消行触发的一次追加 3x3 爆破。
|
|
* @param clearedLines 本次标准消行数量。
|
|
*/
|
|
void ResolveChainBombFollowup(int clearedLines)
|
|
{
|
|
// 没有标准消行时,连环炸弹追加爆破不触发,并清掉挂起标记。
|
|
if (!pendingChainBombFollowup || clearedLines <= 0)
|
|
{
|
|
pendingChainBombFollowup = false;
|
|
return;
|
|
}
|
|
|
|
pendingChainBombFollowup = false;
|
|
|
|
// 追加爆破以第一次爆破落地点为中心,只执行一次 3x3 清除。
|
|
int followupCleared = 0;
|
|
int centerY = pendingChainBombCenter.y;
|
|
int centerX = pendingChainBombCenter.x;
|
|
Point followupCells[9] = {};
|
|
|
|
for (int y = centerY - 1; y <= centerY + 1; y++)
|
|
{
|
|
for (int x = centerX - 1; x <= centerX + 1; x++)
|
|
{
|
|
if (y >= 0 && y < GetRoguePlayableHeight() && x >= 0 && x < nGameWidth && workRegion[y][x] != 0)
|
|
{
|
|
if (followupCleared < 9)
|
|
{
|
|
followupCells[followupCleared].x = x;
|
|
followupCells[followupCleared].y = y;
|
|
}
|
|
workRegion[y][x] = 0;
|
|
followupCleared++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (currentMode == MODE_ROGUE && followupCleared > 0)
|
|
{
|
|
TriggerCellClearEffect(followupCells, followupCleared < 9 ? followupCleared : 9, true);
|
|
int followupScore = 0;
|
|
int followupExp = 0;
|
|
AwardRogueSkillClearRewards(followupCleared, followupScore, followupExp, false);
|
|
|
|
TCHAR followupDetail[128];
|
|
_stprintf_s(
|
|
followupDetail,
|
|
_T("追加爆炸清除 %d 格 +%d 分 +%d EXP"),
|
|
followupCleared,
|
|
followupScore,
|
|
followupExp);
|
|
SetFeedbackMessage(_T("连环炸弹"), followupDetail, 12);
|
|
}
|
|
}
|