再次整理文件结构

This commit is contained in:
2026-05-01 16:03:34 +08:00
parent 7fe0244a99
commit 84017ae6b7
7 changed files with 3121 additions and 3016 deletions
+6
View File
@@ -329,6 +329,12 @@ void DeleteOneLine(int number);
*/
int DeleteLines();
/**
* @brief 判断当前游戏是否已经结束。
* @return 游戏结束返回 true,否则返回 false。
*/
bool GameOver();
/**
* @brief 计算当前活动方块的预测落点。
*/
+49
View File
@@ -21,6 +21,55 @@ extern int pendingLineClearEffectLineCount;
*/
Point GetSpawnPoint(int brickType);
/**
* @brief 收集当前方块将要固定到棋盘上的格子,并写入工作区。
* @param overflowTop 返回是否有方块格位于可视区域顶部之外。
* @param fixedCells 返回普通落地格数组。
* @param fixedCellCount 返回普通落地格数量。
* @param explosiveCells 返回爆破方块落地格数组。
* @param explosiveCellCount 返回爆破格数量。
*/
void CollectAndWriteFixedCells(
bool& overflowTop,
Point fixedCells[],
int& fixedCellCount,
Point explosiveCells[],
int& explosiveCellCount);
/**
* @brief 处理方块固定时的顶部溢出、终末清场和最后一搏。
* @param overflowTop 是否出现顶部溢出。
* @return 游戏可以继续返回 true,需要结束返回 false。
*/
bool ResolveFixingOverflow(bool overflowTop);
/**
* @brief 生成下一枚活动方块,并刷新 Hold、特殊方块和预测落点状态。
*/
void SpawnNextFallingPiece();
/**
* @brief 从底向上扫描满行并删除,记录本次消除的原始行号。
* @param clearedRows 返回最多 8 个被消除行号。
* @param clearedRowCount 返回记录的行号数量。
* @return 本次标准消行数量。
*/
int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount);
/**
* @brief 根据当前界面状态立即播放或暂存消行动画。
* @param clearedRows 已消除行号数组。
* @param clearedRowCount 行号数量。
* @param clearedLines 本次消行数量。
*/
void DispatchLineClearEffect(const int clearedRows[], int clearedRowCount, int clearedLines);
/**
* @brief 处理连环炸弹因消行触发的一次追加爆破。
* @param clearedLines 本次标准消行数量。
*/
void ResolveChainBombFollowup(int clearedLines);
/**
* @brief 重置经典或 Rogue 模式使用的玩家统计数据。
* @param stats 需要重置的统计结构。
+7
View File
@@ -21,3 +21,10 @@ Gdiplus::Bitmap* LoadBackgroundImage();
* @return 成功时返回缓存位图指针,失败或越界时返回 nullptr。
*/
Gdiplus::Bitmap* LoadCreditImage(int index);
/**
* @brief 绘制完整游戏界面,供 TDrawScreen 总入口调用。
* @param hdc 目标绘图设备上下文。
* @param hWnd 当前窗口句柄。
*/
void RenderFullScreen(HDC hdc, HWND hWnd);
+13 -308
View File
@@ -105,77 +105,6 @@ COLORREF BrickColor[7] =
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 计算得到的生成坐标。
*/
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 判断当前方块是否可以继续向下移动。
*
@@ -381,129 +310,6 @@ void DropDown()
}
}
/**
* @brief 收集当前方块将要固定到棋盘上的格子,并标记是否越过顶部。
* @param overflowTop 返回是否有方块格位于可视区域顶部之外。
* @param fixedCells 返回普通落地格,用于后续特殊效果定位。
* @param fixedCellCount 返回普通落地格数量。
* @param explosiveCells 返回爆破方块落地格。
* @param explosiveCellCount 返回爆破方块落地格数量。
*/
static 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。
*/
static 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、特殊方块和预测落点状态。
*/
static void SpawnNextFallingPiece()
{
// 消耗预览队列后重置本回合状态,确保 Hold 和特殊标记只影响新方块。
type = ConsumeNextType();
nType = nextTypes[0];
state = 0;
holdUsedThisTurn = false;
RollCurrentPieceSpecialFlags(true);
point = GetSpawnPoint(type);
target = point;
ComputeTarget();
}
/**
* @brief 将当前活动方块固定到工作区,并生成下一个活动方块。
*
@@ -567,120 +373,6 @@ void DeleteOneLine(int number)
}
}
/**
* @brief 从底向上扫描满行并删除,记录本次消除的原始行号。
* @param clearedRows 返回最多 8 个被消除行号,用于播放消行动画。
* @param clearedRowCount 返回记录的行号数量。
* @return 本次标准消行数量。
*/
static int ScanAndDeleteFullLines(int clearedRows[], int& clearedRowCount)
{
int clearedLines = 0;
clearedRowCount = 0;
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 本次消行数量。
*/
static 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 本次标准消行数量。
*/
static void ResolveChainBombFollowup(int clearedLines)
{
if (!pendingChainBombFollowup || clearedLines <= 0)
{
pendingChainBombFollowup = false;
return;
}
pendingChainBombFollowup = false;
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);
}
}
/**
* @brief 检查并删除所有已满的行,同时更新当前得分。
*
@@ -707,6 +399,19 @@ int DeleteLines()
return clearedLines;
}
/**
* @brief 判断当前游戏是否已经结束。
*
* 老师作业框架中保留该函数名,当前项目内部的结束状态统一记录在
* gameOverFlag 中,因此这里直接返回该标记,避免改变原有流程。
*
* @return 游戏结束返回 true,否则返回 false。
*/
bool GameOver()
{
return gameOverFlag;
}
/**
* @brief 计算当前活动方块的预测落点位置。
*
File diff suppressed because it is too large Load Diff
+316
View File
@@ -0,0 +1,316 @@
#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;
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;
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);
}
}
File diff suppressed because it is too large Load Diff