From 1c000c3c219ea795f9e7b57b7b87b3b4189060f0 Mon Sep 17 00:00:00 2001 From: qihuanye <2728290997@qq.com> Date: Tue, 28 Apr 2026 23:18:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=BC=BA=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/include/Tetris.h | 274 ++++++++++++++++++ src/include/TetrisAppInternal.h | 66 +++++ src/include/TetrisAssets.h | 11 + src/include/TetrisLogicInternal.h | 57 ++++ src/include/TetrisRenderInternal.h | 8 + src/include/resource.h | 5 + src/include/stdafx.h | 8 +- src/include/targetver.h | 5 + src/source/Tetris.cpp | 84 +++++- src/source/TetrisLogic.cpp | 11 +- src/source/TetrisRender.cpp | 28 ++ src/source/app/TetrisInput.cpp | 45 +++ src/source/app/TetrisLayout.cpp | 100 +++++++ src/source/app/TetrisMedia.cpp | 13 + src/source/app/TetrisTimers.cpp | 21 ++ src/source/common/TetrisAssets.cpp | 19 ++ .../extensions/TetrisGameExtensions.cpp | 61 +++- src/source/logic/TetrisPieceEffects.cpp | 20 ++ src/source/render/TetrisRenderAssets.cpp | 14 + src/source/rogue/TetrisRogue.cpp | 43 +++ src/source/stdafx.cpp | 7 +- 21 files changed, 888 insertions(+), 12 deletions(-) diff --git a/src/include/Tetris.h b/src/include/Tetris.h index 3a6577d..f3d3abe 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -1,5 +1,10 @@ #pragma once +/** + * @file Tetris.h + * @brief 定义俄罗斯方块项目的全局常量、结构体、枚举、全局状态和公开函数接口。 + */ + #include "resource.h" #include "stdafx.h" #include @@ -264,60 +269,329 @@ extern bool currentPieceIsRainbow; extern int bricks[7][4][4][4]; extern COLORREF BrickColor[7]; +/** + * @brief 判断当前活动方块是否还能向下移动一格。 + * @return 可以下落返回 true,否则返回 false。 + */ bool CanMoveDown(); + +/** + * @brief 判断当前活动方块是否还能向左移动一格。 + * @return 可以左移返回 true,否则返回 false。 + */ bool CanMoveLeft(); + +/** + * @brief 判断当前活动方块是否还能向右移动一格。 + * @return 可以右移返回 true,否则返回 false。 + */ bool CanMoveRight(); + +/** + * @brief 将当前活动方块向下移动一格。 + */ void MoveDown(); + +/** + * @brief 将当前活动方块向左移动一格。 + */ void MoveLeft(); + +/** + * @brief 将当前活动方块向右移动一格。 + */ void MoveRight(); + +/** + * @brief 尝试旋转当前活动方块,Rogue 完美旋转会额外尝试左右偏移。 + */ void Rotate(); + +/** + * @brief 将当前活动方块直接下落到预测落点。 + */ void DropDown(); + +/** + * @brief 将当前活动方块固定到棋盘并生成下一块。 + */ void Fixing(); + +/** + * @brief 删除指定行并让上方棋盘整体下落。 + * @param number 要删除的棋盘行号。 + */ void DeleteOneLine(int number); + +/** + * @brief 扫描棋盘、删除所有满行并触发消行结算。 + * @return 本次删除的行数。 + */ int DeleteLines(); + +/** + * @brief 计算当前活动方块的预测落点。 + */ void ComputeTarget(); + +/** + * @brief 重置棋盘、方块、统计和视觉状态,开始一局新游戏。 + */ void Restart(); + +/** + * @brief 按指定模式开始新游戏。 + * @param mode 游戏模式,取值来自 GameMode。 + */ void StartGameWithMode(int mode); + +/** + * @brief 返回主菜单并清理临时玩法与界面状态。 + */ void ReturnToMainMenu(); + +/** + * @brief 复活视频播放成功后恢复游戏并清理顶部空间。 + */ void ReviveAfterVideo(); + +/** + * @brief 从帮助页进入 Rogue 技能演示的第一项。 + */ void StartRogueSkillDemo(); + +/** + * @brief 从帮助页进入指定 Rogue 技能演示。 + * @param demoIndex 技能演示序号。 + */ void StartRogueSkillDemoAt(int demoIndex); + +/** + * @brief 重新开始当前 Rogue 技能演示场景。 + */ void RestartCurrentRogueSkillDemo(); + +/** + * @brief 判断当前是否处于 Rogue 技能演示模式。 + * @return 演示模式中返回 true,否则返回 false。 + */ bool IsRogueSkillDemoMode(); + +/** + * @brief 推进 Rogue 技能演示计时。 + * @return 演示模式正在运行返回 true,否则返回 false。 + */ bool TickRogueSkillDemo(); + +/** + * @brief 切换到下一项 Rogue 技能演示。 + */ void AdvanceRogueSkillDemo(); + +/** + * @brief 获取 Rogue 技能演示条目数量。 + * @return 可选择的演示条目总数。 + */ int GetRogueSkillDemoCount(); + +/** + * @brief 获取指定 Rogue 技能演示名称。 + * @param demoIndex 技能演示序号。 + * @return 名称字符串,越界时返回空字符串。 + */ const TCHAR* GetRogueSkillDemoName(int demoIndex); + +/** + * @brief 获取指定 Rogue 技能演示说明。 + * @param demoIndex 技能演示序号。 + * @return 说明字符串,越界时返回空字符串。 + */ const TCHAR* GetRogueSkillDemoDetail(int demoIndex); + +/** + * @brief 获取当前 Rogue 技能演示名称。 + * @return 当前名称,非演示模式返回空字符串。 + */ const TCHAR* GetCurrentRogueSkillDemoName(); + +/** + * @brief 设置右侧战斗日志反馈信息。 + * @param title 反馈标题。 + * @param detail 反馈详情。 + * @param ticks 保持显示的游戏计时次数。 + */ void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); + +/** + * @brief 打开帮助首页。 + */ void OpenRulesScreen(); + +/** + * @brief 打开 Rogue 技能演示选择页。 + */ void OpenSkillDemoScreen(); + +/** + * @brief 打开致谢页。 + */ void OpenCreditScreen(); + +/** + * @brief 切换致谢页图片。 + * @param direction 小于 0 向前切换,大于 0 向后切换。 + */ void ChangeCreditPage(int direction); + +/** + * @brief 打开 Rogue 升级选择界面。 + */ void OpenUpgradeMenu(); + +/** + * @brief 确认当前升级选择并恢复游戏流程。 + */ void ConfirmUpgradeSelection(); + +/** + * @brief 重置升级选择界面状态。 + */ void ResetUpgradeUiState(); + +/** + * @brief 使用或解锁后处理 Hold 备用仓逻辑。 + */ void HoldCurrentPiece(); + +/** + * @brief 使用清屏炸弹主动技能。 + */ void UseScreenBomb(); + +/** + * @brief 使用黑洞主动技能。 + */ void UseBlackHole(); + +/** + * @brief 使用空中换形主动技能。 + */ void UseAirReshape(); + +/** + * @brief 重置 Rogue 待播放视觉事件。 + */ void ResetPendingRogueVisualEvents(); + +/** + * @brief 清空所有视觉效果状态。 + */ void ResetVisualEffects(); + +/** + * @brief 推进视觉效果动画。 + * @return 仍有动画需要刷新返回 true,否则返回 false。 + */ bool TickVisualEffects(); + +/** + * @brief 推进致谢页切换动画。 + * @return 需要刷新界面返回 true,否则返回 false。 + */ bool TickCreditAnimation(); + +/** + * @brief 触发标准消行动画。 + * @param rows 被消除的行号数组。 + * @param rowCount 行号数量。 + * @param linesCleared 实际消除行数。 + */ void TriggerLineClearEffect(const int* rows, int rowCount, int linesCleared); + +/** + * @brief 播放之前因升级界面暂存的消行动画。 + */ void PlayPendingLineClearEffect(); + +/** + * @brief 触发指定棋盘格的默认清除特效。 + * @param cells 被清除格子数组。 + * @param cellCount 格子数量。 + * @param strongBurst 是否使用更强的爆裂粒子。 + */ void TriggerCellClearEffect(const Point* cells, int cellCount, bool strongBurst); + +/** + * @brief 触发指定棋盘格的自定义颜色清除特效。 + * @param cells 被清除格子数组。 + * @param cellCount 格子数量。 + * @param flashColor 高亮颜色。 + * @param strongBurst 是否使用更强的爆裂粒子。 + */ void TriggerColoredCellClearEffect(const Point* cells, int cellCount, COLORREF flashColor, bool strongBurst); + +/** + * @brief 记录一个固定方块受重力下落的轨迹。 + * @param x 棋盘列号。 + * @param fromY 下落起始行号。 + * @param toY 下落目标行号。 + * @param cellValue 方块格子数值。 + */ void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue); + +/** + * @brief 为 Rogue 主动或特殊技能清除格子发放奖励。 + * @param clearedCells 清除格子数。 + * @param scoreGain 返回本次得分增量。 + * @param expGain 返回本次经验增量。 + * @param allowLevelProgress 是否允许本次奖励触发升级流程。 + */ void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain, bool allowLevelProgress); + +/** + * @brief 检查 Rogue 经验是否达到升级条件。 + */ void CheckRogueLevelProgress(); + +/** + * @brief 对棋盘固定方块应用重力下落。 + */ void ApplyBoardGravity(); + +/** + * @brief 计算当前 Rogue 模式下落间隔。 + * @return 下落计时器间隔,单位毫秒。 + */ int GetRogueFallInterval(); + +/** + * @brief 获取 Rogue 当前可操作棋盘高度。 + * @return 未被底部封锁占用的行数。 + */ int GetRoguePlayableHeight(); + +/** + * @brief 获取 Rogue 难度系统当前封锁的底部行数。 + * @return 封锁行数。 + */ int GetRogueLockedRows(); + +/** + * @brief 按经过时间推进 Rogue 难度。 + * @param elapsedMs 本次推进的时间,单位毫秒。 + */ void AdvanceRogueDifficulty(int elapsedMs); + +/** + * @brief 获取进化强化的合成路线文本。 + * @param upgradeId 强化编号。 + * @return 路线文本;普通强化返回空或空指针。 + */ const TCHAR* GetUpgradeSynthesisPath(int upgradeId); +/** + * @brief 绘制当前窗口中的完整游戏界面。 + * @param hdc 目标绘图设备上下文。 + * @param hWnd 当前窗口句柄,用于读取客户区大小。 + */ void TDrawScreen(HDC hdc, HWND hWnd); diff --git a/src/include/TetrisAppInternal.h b/src/include/TetrisAppInternal.h index 8a15778..f4f3fb8 100644 --- a/src/include/TetrisAppInternal.h +++ b/src/include/TetrisAppInternal.h @@ -1,5 +1,10 @@ #pragma once +/** + * @file TetrisAppInternal.h + * @brief 声明窗口布局、输入、计时器和媒体播放等应用层内部接口。 + */ + #include "Tetris.h" constexpr int GAME_TIMER_ID = 1; @@ -22,106 +27,157 @@ struct LayoutMetrics /** * @brief 根据当前窗口大小计算整体界面缩放与偏移。 + * @param hWnd 当前窗口句柄。 + * @return 布局缩放、偏移和网格尺寸。 */ LayoutMetrics GetLayoutMetrics(HWND hWnd); /** * @brief 按当前布局比例缩放一个尺寸值。 + * @param metrics 当前布局参数。 + * @param value 设计稿尺寸值。 + * @return 缩放后的像素尺寸。 */ int ScaleValue(const LayoutMetrics& metrics, int value); /** * @brief 按当前布局比例缩放横坐标并叠加窗口偏移。 + * @param metrics 当前布局参数。 + * @param value 设计稿横坐标。 + * @return 实际窗口横坐标。 */ int ScaleXValue(const LayoutMetrics& metrics, int value); /** * @brief 按当前布局比例缩放纵坐标并叠加窗口偏移。 + * @param metrics 当前布局参数。 + * @param value 设计稿纵坐标。 + * @return 实际窗口纵坐标。 */ int ScaleYValue(const LayoutMetrics& metrics, int value); /** * @brief 获取主菜单选项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 菜单选项序号。 + * @return 选项在窗口中的矩形区域。 */ RECT GetMenuOptionRect(HWND hWnd, int index); /** * @brief 获取帮助页选项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 帮助首页选项序号。 + * @return 选项在窗口中的矩形区域。 */ RECT GetHelpOptionRect(HWND hWnd, int index); /** * @brief 获取技能演示列表项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 技能演示条目序号。 + * @return 条目在窗口中的矩形区域。 */ RECT GetHelpSkillDemoItemRect(HWND hWnd, int index); /** * @brief 获取帮助页底部返回提示的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 返回提示在窗口中的矩形区域。 */ RECT GetHelpBackHintRect(HWND hWnd); /** * @brief 获取致谢页左右切换按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param direction 小于 0 表示左箭头,大于 0 表示右箭头。 + * @return 切换按钮在窗口中的矩形区域。 */ RECT GetCreditArrowRect(HWND hWnd, int direction); /** * @brief 获取升级选择卡片的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 强化卡片序号。 + * @return 卡片在窗口中的矩形区域。 */ RECT GetUpgradeCardRect(HWND hWnd, int index); /** * @brief 获取暂停或结束覆盖层按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 按钮序号。 + * @param buttonCount 覆盖层当前按钮总数。 + * @return 按钮在窗口中的矩形区域。 */ RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount); /** * @brief 获取左上角返回按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 返回按钮在窗口中的矩形区域。 */ RECT GetBackButtonRect(HWND hWnd); /** * @brief 获取右下角音乐按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 音乐按钮在窗口中的矩形区域。 */ RECT GetMusicButtonRect(HWND hWnd); /** * @brief 判断点坐标是否落在矩形内部。 + * @param rect 待判断矩形。 + * @param x 点的横坐标。 + * @param y 点的纵坐标。 + * @return 点在矩形内返回 true,否则返回 false。 */ bool IsPointInRect(const RECT& rect, int x, int y); /** * @brief 将滚动偏移按步长调整并限制在有效范围内。 + * @param scrollOffset 需要修改的滚动偏移。 + * @param delta 本次滚动增量。 */ void AdjustScrollOffset(int& scrollOffset, int delta); /** * @brief 获取适配当前窗口缩放的一次滚动步长。 + * @param hWnd 当前窗口句柄。 + * @param baseStep 设计稿中的基础滚动步长。 + * @return 缩放后的滚动步长。 */ int GetScrollStep(HWND hWnd, int baseStep); /** * @brief 重置主下落定时器。 + * @param hWnd 当前窗口句柄。 */ void ResetGameTimer(HWND hWnd); /** * @brief 启动游戏、特效和致谢页动画定时器。 + * @param hWnd 当前窗口句柄。 */ void StartAppTimers(HWND hWnd); /** * @brief 停止游戏、特效和致谢页动画定时器。 + * @param hWnd 当前窗口句柄。 */ void StopAppTimers(HWND hWnd); /** * @brief 处理致谢页高频动画刷新消息。 + * @param hWnd 当前窗口句柄。 */ void HandleCreditTick(HWND hWnd); /** * @brief 处理窗口定时器消息。 + * @param hWnd 当前窗口句柄。 + * @param timerId 触发的定时器编号。 */ void HandleTimerMessage(HWND hWnd, WPARAM timerId); @@ -137,25 +193,35 @@ void StopBackgroundMusic(); /** * @brief 切换背景音乐开关并刷新窗口。 + * @param hWnd 当前窗口句柄。 */ void ToggleBackgroundMusic(HWND hWnd); /** * @brief 播放复活视频,播放成功返回 true。 + * @param hWnd 当前窗口句柄,用于 MCI 播放和父窗口绑定。 + * @return 播放成功返回 true,否则返回 false。 */ bool PlayReviveVideo(HWND hWnd); /** * @brief 处理鼠标左键释放事件,返回是否已处理。 + * @param hWnd 当前窗口句柄。 + * @param lParam 鼠标消息坐标参数。 + * @return 事件已被界面逻辑消费返回 true,否则返回 false。 */ bool HandleMouseClick(HWND hWnd, LPARAM lParam); /** * @brief 处理鼠标滚轮事件。 + * @param hWnd 当前窗口句柄。 + * @param wParam 鼠标滚轮消息参数。 */ void HandleMouseWheel(HWND hWnd, WPARAM wParam); /** * @brief 处理键盘按键事件。 + * @param hWnd 当前窗口句柄。 + * @param wParam 按键虚拟键码。 */ void HandleKeyDown(HWND hWnd, WPARAM wParam); diff --git a/src/include/TetrisAssets.h b/src/include/TetrisAssets.h index 10e8b42..0fdb957 100644 --- a/src/include/TetrisAssets.h +++ b/src/include/TetrisAssets.h @@ -1,19 +1,30 @@ #pragma once +/** + * @file TetrisAssets.h + * @brief 声明资源路径拼接和文件存在性检查工具函数。 + */ + #include "stdafx.h" #include /** * @brief 根据程序所在目录拼出项目资源文件的绝对路径。 + * @param relativePath 相对于项目根目录的资源路径。 + * @return 规范化后的绝对路径;解析失败时返回拼接路径。 */ std::wstring BuildAssetPath(const wchar_t* relativePath); /** * @brief 根据当前工作目录拼出项目资源文件的绝对路径。 + * @param relativePath 相对于当前工作目录的资源路径。 + * @return 规范化后的绝对路径;解析失败时返回拼接路径。 */ std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath); /** * @brief 判断指定路径是否存在且不是目录。 + * @param path 待检查的文件路径。 + * @return 文件存在且不是目录返回 true,否则返回 false。 */ bool FileExists(const std::wstring& path); diff --git a/src/include/TetrisLogicInternal.h b/src/include/TetrisLogicInternal.h index f0d9f61..7eef381 100644 --- a/src/include/TetrisLogicInternal.h +++ b/src/include/TetrisLogicInternal.h @@ -1,5 +1,10 @@ #pragma once +/** + * @file TetrisLogicInternal.h + * @brief 声明棋盘逻辑、Rogue 结算和特殊方块效果使用的内部接口。 + */ + #include "Tetris.h" extern Point pendingChainBombCenter; @@ -11,86 +16,129 @@ extern int pendingLineClearEffectLineCount; /** * @brief 计算指定方块在棋盘顶部的统一生成位置。 + * @param brickType 方块类型编号。 + * @return 生成坐标,可能位于可视区域上方。 */ Point GetSpawnPoint(int brickType); /** * @brief 重置经典或 Rogue 模式使用的玩家统计数据。 + * @param stats 需要重置的统计结构。 + * @param useRogueRules 是否按 Rogue 模式设置初始经验需求。 */ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules); /** * @brief 设置界面右侧显示的即时反馈标题、内容和持续时间。 + * @param title 反馈标题。 + * @param detail 反馈详情。 + * @param ticks 显示持续的游戏计时次数。 */ void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks); /** * @brief 判断指定方块、旋转状态和位置是否可以合法放置。 + * @param pieceType 方块类型编号。 + * @param pieceState 方块旋转状态。 + * @param position 待检测的左上角坐标。 + * @return 可以放置返回 true,否则返回 false。 */ bool IsPiecePlacementValid(int pieceType, int pieceState, Point position); /** * @brief 判断棋盘格是否为彩虹特殊方块。 + * @param cellValue 棋盘格存储值。 + * @return 彩虹方块返回 true,否则返回 false。 */ bool IsRainbowBoardCell(int cellValue); /** * @brief 触发小型黑洞并返回被清除的固定方块数量。 + * @param maxCellsToClear 最多清除的格子数。 + * @return 实际清除格子数。 */ int TriggerMiniBlackHole(int maxCellsToClear); /** * @brief 触发彩虹方块行清除与覆盖行染色效果。 + * @param anchorRow 作为主色判断的中心行。 + * @param minRow 允许染色范围的最小行。 + * @param maxRow 允许染色范围的最大行。 + * @param recoloredCount 返回被染色的格子数。 + * @return 被清除的主色格子数。 */ int TriggerRainbowColorShift(int anchorRow, int minRow, int maxRow, int& recoloredCount); /** * @brief 引爆清屏炸弹并返回清除格数。 + * @return 实际清除格子数。 */ int TriggerScreenBomb(); /** * @brief 清除指定中心点周围的爆破范围并返回清除格数。 + * @param centerY 爆破中心行。 + * @param centerX 爆破中心列。 + * @return 实际清除格子数。 */ int ClearExplosiveAreaAt(int centerY, int centerX); /** * @brief 清除指定列并返回清除格数。 + * @param column 目标列号。 + * @return 实际清除格子数。 */ int ClearColumnAt(int column); /** * @brief 使用指定颜色特效清除指定列并返回清除格数。 + * @param column 目标列号。 + * @param flashColor 清除高亮颜色。 + * @return 实际清除格子数。 */ int ClearColumnAtWithColor(int column, COLORREF flashColor); /** * @brief 清除指定行并返回清除格数。 + * @param row 目标行号。 + * @return 实际清除格子数。 */ int ClearRowAt(int row); /** * @brief 尝试填补局部空洞以稳定棋盘结构。 + * @return 实际填补格子数。 */ int TryStabilizeBoard(); /** * @brief 为当前方块刷新 Rogue 特殊方块标记。 + * @param allowRandomSpecials 是否允许按强化概率随机生成特殊方块。 */ void RollCurrentPieceSpecialFlags(bool allowRandomSpecials); /** * @brief 暂存消行动画,等待升级选择结束后再播放。 + * @param rows 被消除的行号数组。 + * @param rowCount 行号数量。 + * @param linesCleared 实际消除行数。 */ void QueueLineClearEffect(const int* rows, int rowCount, int linesCleared); /** * @brief 记录固定方块受重力下落的轨迹,用于播放纵向残影特效。 + * @param x 棋盘列号。 + * @param fromY 起始行号。 + * @param toY 目标行号。 + * @param cellValue 方块格子值。 */ void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue); /** * @brief 尝试把旋转后的方块横向偏移指定格数后放置。 + * @param nextState 旋转后的状态编号。 + * @param offsetX 横向试探偏移。 + * @return 偏移后可以放置返回 true,否则返回 false。 */ bool TryRotateWithOffset(int nextState, int offsetX); @@ -101,20 +149,29 @@ void ResetNextQueue(); /** * @brief 消费队首下一方块并补充新的预览方块。 + * @return 新的当前方块类型编号。 */ int ConsumeNextType(); /** * @brief 结算一次标准消行带来的 Rogue 玩法效果。 + * @param linesCleared 本次标准消行数量。 */ void ApplyLineClearResult(int linesCleared); /** * @brief 结算彩虹方块固定后的染色和清除效果。 + * @param overflowTop 固定时是否已经越过顶部。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 */ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fixedCellCount); /** * @brief 结算爆破、激光、十字和稳定结构等特殊落地效果。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 + * @param explosiveCells 爆破方块写入棋盘的格子数组。 + * @param explosiveCellCount 爆破格子数量。 */ void ApplySpecialLandingEffects(const Point* fixedCells, int fixedCellCount, const Point* explosiveCells, int explosiveCellCount); diff --git a/src/include/TetrisRenderInternal.h b/src/include/TetrisRenderInternal.h index d3f518b..269971e 100644 --- a/src/include/TetrisRenderInternal.h +++ b/src/include/TetrisRenderInternal.h @@ -1,15 +1,23 @@ #pragma once +/** + * @file TetrisRenderInternal.h + * @brief 声明渲染模块内部使用的 GDI+ 图片加载接口。 + */ + #include "Tetris.h" #include #include /** * @brief 加载并缓存主背景图片。 + * @return 成功时返回缓存位图指针,失败时返回 nullptr。 */ Gdiplus::Bitmap* LoadBackgroundImage(); /** * @brief 按序号加载并缓存致谢页图片。 + * @param index 致谢页图片序号。 + * @return 成功时返回缓存位图指针,失败或越界时返回 nullptr。 */ Gdiplus::Bitmap* LoadCreditImage(int index); diff --git a/src/include/resource.h b/src/include/resource.h index 5ad2731..9f8f29b 100644 --- a/src/include/resource.h +++ b/src/include/resource.h @@ -1,5 +1,10 @@ #pragma once +/** + * @file resource.h + * @brief 定义菜单、图标、对话框和命令等 Windows 资源编号。 + */ + //{{NO_DEPENDENCIES}} // Microsoft Visual C++ 生成的包含文件。 // 供 Tetris.rc 使用 diff --git a/src/include/stdafx.h b/src/include/stdafx.h index d85f9f5..1a62466 100644 --- a/src/include/stdafx.h +++ b/src/include/stdafx.h @@ -1,7 +1,7 @@ -// stdafx.h : 标准系统包含文件的包含文件, -// 或是经常使用但不常更改的 -// 特定于项目的包含文件 -// +/** + * @file stdafx.h + * @brief 集中包含 Windows、C 运行时和项目常用基础头文件。 + */ #pragma once diff --git a/src/include/targetver.h b/src/include/targetver.h index 6347f6d..3d8b85c 100644 --- a/src/include/targetver.h +++ b/src/include/targetver.h @@ -1,5 +1,10 @@ #pragma once +/** + * @file targetver.h + * @brief 设置 Windows SDK 目标平台版本,供 Win32 头文件选择可用 API。 + */ + // 包括 SDKDDKVer.h 将定义可用的最高版本的 Windows 平台。 // 如果要为以前的 Windows 平台生成应用程序,请包括 WinSDKVer.h,并将 diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index ce80cf8..606bd6c 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file Tetris.cpp + * @brief 实现 Win32 程序入口、主窗口创建、消息分发和双缓冲绘制流程。 + */ + #include "Tetris.h" #include "TetrisAppInternal.h" @@ -10,11 +15,52 @@ TCHAR szWindowClass[MAX_LOADSTRING]; bool bgmEnabled = true; +/** + * @brief 注册主窗口类,供 CreateWindow 创建游戏窗口使用。 + * @param hInstance 当前程序实例句柄。 + * @return RegisterClassEx 返回的窗口类原子值。 + */ ATOM MyRegisterClass(HINSTANCE hInstance); -BOOL InitInstance(HINSTANCE, int); -LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); -INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); +/** + * @brief 创建、显示并更新主窗口。 + * @param hInstance 当前程序实例句柄。 + * @param nCmdShow 窗口初始显示方式。 + * @return 创建成功返回 TRUE,否则返回 FALSE。 + */ +BOOL InitInstance(HINSTANCE hInstance, int nCmdShow); + +/** + * @brief 主窗口消息回调,分发绘制、输入、计时器和生命周期消息。 + * @param hWnd 当前窗口句柄。 + * @param message Windows 消息编号。 + * @param wParam 消息附加参数。 + * @param lParam 消息附加参数。 + * @return 消息处理结果。 + */ +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); + +/** + * @brief 关于对话框消息回调。 + * @param hDlg 对话框窗口句柄。 + * @param message Windows 消息编号。 + * @param wParam 消息附加参数。 + * @param lParam 消息附加参数。 + * @return 已处理消息返回 TRUE,否则返回 FALSE。 + */ +INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); + +/** + * @brief Windows 程序入口,完成窗口注册、创建和主消息循环。 + * + * 函数启动时会设置 DPI 感知,加载窗口标题和类名资源,然后进入标准消息循环。 + * + * @param hInstance 当前程序实例句柄。 + * @param hPrevInstance 旧版 Windows 保留参数,本程序不使用。 + * @param lpCmdLine 命令行字符串,本程序不使用。 + * @param nCmdShow 窗口初始显示方式。 + * @return 程序退出码。 + */ int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, @@ -23,6 +69,7 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); + // 旧版工程模板没有自动声明 DPI 感知,这里动态获取函数避免系统不支持时报错。 HMODULE user32Module = GetModuleHandle(_T("user32.dll")); if (user32Module != nullptr) { @@ -49,6 +96,7 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TETRIS)); MSG msg; + // 标准消息循环负责把键盘、鼠标、计时器和绘制消息送入窗口过程。 while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) @@ -61,6 +109,11 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, return (int)msg.wParam; } +/** + * @brief 注册主窗口类,供 CreateWindow 创建游戏窗口使用。 + * @param hInstance 当前程序实例句柄。 + * @return RegisterClassEx 返回的窗口类原子值。 + */ ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; @@ -81,6 +134,12 @@ ATOM MyRegisterClass(HINSTANCE hInstance) return RegisterClassEx(&wcex); } +/** + * @brief 创建、显示并更新主窗口。 + * @param hInstance 当前程序实例句柄。 + * @param nCmdShow 窗口初始显示方式。 + * @return 创建成功返回 TRUE,否则返回 FALSE。 + */ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { RECT rect = { 0, 0, WINDOW_CLIENT_WIDTH, WINDOW_CLIENT_HEIGHT }; @@ -112,11 +171,20 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) return TRUE; } +/** + * @brief 主窗口消息回调,分发绘制、输入、计时器和生命周期消息。 + * @param hWnd 当前窗口句柄。 + * @param message Windows 消息编号。 + * @param wParam 消息附加参数。 + * @param lParam 消息附加参数。 + * @return 消息处理结果。 + */ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: + // 创建阶段统一启动随机数、音乐和定时器,保证进入菜单时界面已经可刷新。 timeBeginPeriod(1); srand((unsigned int)time(nullptr)); ReturnToMainMenu(); @@ -170,6 +238,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 1; case WM_PAINT: { + // 使用内存 DC 双缓冲绘制,减少窗口缩放和动画刷新时的闪烁。 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); RECT clientRect; @@ -199,6 +268,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_DESTROY: + // 退出前释放定时器和音频设备,避免 MCI 或多媒体定时器残留。 StopAppTimers(hWnd); StopBackgroundMusic(); timeEndPeriod(1); @@ -211,6 +281,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; } +/** + * @brief 关于对话框消息回调。 + * @param hDlg 对话框窗口句柄。 + * @param message Windows 消息编号。 + * @param wParam 消息附加参数。 + * @param lParam 消息附加参数。 + * @return 已处理消息返回 TRUE,否则返回 FALSE。 + */ INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 45e308a..e44de81 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisLogic.cpp + * @brief 实现基础俄罗斯方块的移动、旋转、固定、消行、落点计算和重开逻辑。 + */ + #include "Tetris.h" #include "TetrisLogicInternal.h" @@ -332,7 +337,7 @@ void MoveRight() * * 游戏中的每种方块都预置了 4 种旋转状态,该函数会先尝试切换到下一状态, * 然后检查旋转后的方块是否越界或与固定方块重叠。 - * 如果旋转后的状态非法,则恢复到旋转前的状态。 + * 如果旋转后的状态非法,Rogue 的完美旋转会继续尝试左右各偏移一格。 */ void Rotate() { @@ -522,6 +527,8 @@ void DeleteOneLine(int number) * 如果某一行全部非 0,则调用 DeleteOneLine 删除该行, * 并将该行上方的内容整体下移。为了避免连续满行被漏检, * 删除后会继续检查当前行号。每成功消除 1 行,当前得分增加 100 分。 + * + * @return 本次实际消除的行数。 */ int DeleteLines() { @@ -557,6 +564,7 @@ int DeleteLines() } } + // 消行数量先进入玩法结算,再根据是否正在升级决定动画立即播放还是暂存。 ApplyLineClearResult(clearedLines); if (currentScreen == SCREEN_UPGRADE) { @@ -567,6 +575,7 @@ int DeleteLines() TriggerLineClearEffect(clearedRows, clearedRowCount, clearedLines); } + // 连环炸弹的追加爆破只在爆破方块导致后续消行时触发一次。 if (pendingChainBombFollowup && clearedLines > 0) { pendingChainBombFollowup = false; diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 29a4be3..b975ef4 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisRender.cpp + * @brief 实现主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级界面的完整绘制逻辑。 + */ + #include "Tetris.h" #include "TetrisRenderInternal.h" #include @@ -8,6 +13,11 @@ using namespace Gdiplus; +/** + * @brief 按颜色缓存粒子画刷,减少动画绘制时重复创建 GDI 对象。 + * @param color 画刷颜色。 + * @return 可复用的实心画刷句柄;缓存满时返回临时新建画刷。 + */ static HBRUSH GetCachedParticleBrush(COLORREF color) { static COLORREF cachedColors[16] = {}; @@ -34,11 +44,22 @@ static HBRUSH GetCachedParticleBrush(COLORREF color) return CreateSolidBrush(color); } +/** + * @brief 绘制当前游戏窗口的完整界面。 + * + * 函数按当前屏幕状态绘制主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级选择。 + * 由于大量绘图辅助逻辑共享当前缩放、字体和颜色,保持在同一函数内集中管理, + * 避免拆分时改变 GDI 对象的选择和释放顺序。 + * + * @param hdc 目标绘图设备上下文。 + * @param hWnd 当前窗口句柄,用于读取客户区大小。 + */ void TDrawScreen(HDC hdc, HWND hWnd) { RECT clientRect; GetClientRect(hWnd, &clientRect); + // 根据窗口大小计算统一缩放比例,所有坐标都从设计稿尺寸映射到实际窗口。 int clientWidth = clientRect.right - clientRect.left; int clientHeight = clientRect.bottom - clientRect.top; int scaleX = MulDiv(clientWidth, 1000, WINDOW_CLIENT_WIDTH); @@ -119,6 +140,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) const BYTE panelNestedAlpha = 128; const BYTE panelStrongAlpha = 168; + // 背景图片存在时优先绘制图片并叠加浅色遮罩,否则使用纯色和装饰形状。 Bitmap* backgroundImage = LoadBackgroundImage(); if (backgroundImage != nullptr) { @@ -155,6 +177,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DeleteObject(blobBrushB); } + // 本函数集中创建字体,所有提前 return 分支都要在返回前释放这些 GDI 对象。 HFONT titleFont = CreateFont( -SS(36), 0, 0, 0, FW_BOLD, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS, CLEARTYPE_NATURAL_QUALITY, @@ -178,6 +201,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, textColor); + // 以下局部绘图函数共享 hdc、缩放函数和颜色,避免每个小控件重复计算上下文。 auto DrawPanelCard = [&](const RECT& rect, COLORREF fillColor, COLORREF borderColor, int radius) { HBRUSH cardBrush = CreateSolidBrush(fillColor); @@ -312,6 +336,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DeleteObject(backPen); }; + // 主菜单独立绘制并提前返回,避免后续游戏棋盘和侧栏在菜单后面继续绘制。 if (currentScreen == SCREEN_MENU) { RECT menuCard = @@ -439,6 +464,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) return; } + // 帮助、规则、图鉴、致谢和技能演示入口共用规则页卡片框架。 if (currentScreen == SCREEN_RULES) { RECT rulesCard = @@ -2300,6 +2326,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) DT_LEFT | DT_TOP | DT_WORDBREAK); } + // 暂停和结束覆盖层只盖住棋盘区域,让两侧战斗信息仍然可见。 if (suspendFlag || gameOverFlag) { RECT overlayRect = @@ -2427,6 +2454,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) } } + // 升级选择界面在当前战局上方绘制半透明遮罩,保留背景局势作为上下文。 if (currentScreen == SCREEN_UPGRADE) { RECT dimRect = diff --git a/src/source/app/TetrisInput.cpp b/src/source/app/TetrisInput.cpp index bb2679b..3aab344 100644 --- a/src/source/app/TetrisInput.cpp +++ b/src/source/app/TetrisInput.cpp @@ -1,8 +1,14 @@ #include "stdafx.h" +/** + * @file TetrisInput.cpp + * @brief 实现鼠标和键盘输入处理,负责菜单、帮助、升级界面和游戏操作分发。 + */ + #include "TetrisAppInternal.h" /** * @brief 打开当前菜单选中的页面或开始对应模式。 + * @param hWnd 当前窗口句柄,用于重置计时器和触发重绘。 */ static void ActivateMenuSelection(HWND hWnd) { @@ -28,6 +34,7 @@ static void ActivateMenuSelection(HWND hWnd) /** * @brief 处理返回按钮的统一点击行为。 + * @param hWnd 当前窗口句柄,用于触发重绘。 */ static void HandleBackButtonClick(HWND hWnd) { @@ -50,6 +57,10 @@ static void HandleBackButtonClick(HWND hWnd) /** * @brief 处理主菜单点击。 + * @param hWnd 当前窗口句柄。 + * @param mouseX 鼠标横坐标。 + * @param mouseY 鼠标纵坐标。 + * @return 当前界面是菜单时返回 true,否则返回 false。 */ static bool HandleMenuClick(HWND hWnd, int mouseX, int mouseY) { @@ -75,6 +86,10 @@ static bool HandleMenuClick(HWND hWnd, int mouseX, int mouseY) /** * @brief 处理规则、帮助、致谢和技能演示页点击。 + * @param hWnd 当前窗口句柄。 + * @param mouseX 鼠标横坐标。 + * @param mouseY 鼠标纵坐标。 + * @return 当前界面是帮助页时返回 true,否则返回 false。 */ static bool HandleRulesClick(HWND hWnd, int mouseX, int mouseY) { @@ -85,6 +100,7 @@ static bool HandleRulesClick(HWND hWnd, int mouseX, int mouseY) if (helpState.currentPage == 0) { + // 帮助首页的四个入口分别进入介绍、操作、图鉴和技能演示页。 for (int i = 0; i < helpState.optionCount; i++) { if (IsPointInRect(GetHelpOptionRect(hWnd, i), mouseX, mouseY)) @@ -124,6 +140,7 @@ static bool HandleRulesClick(HWND hWnd, int mouseX, int mouseY) return true; } + // 技能演示页的列表项直接启动对应预设棋盘。 int demoCount = GetRogueSkillDemoCount(); for (int i = 0; i < demoCount; i++) { @@ -162,6 +179,10 @@ static bool HandleRulesClick(HWND hWnd, int mouseX, int mouseY) /** * @brief 处理升级选择界面点击。 + * @param hWnd 当前窗口句柄。 + * @param mouseX 鼠标横坐标。 + * @param mouseY 鼠标纵坐标。 + * @return 当前界面是升级选择时返回 true,否则返回 false。 */ static bool HandleUpgradeClick(HWND hWnd, int mouseX, int mouseY) { @@ -178,6 +199,7 @@ static bool HandleUpgradeClick(HWND hWnd, int mouseX, int mouseY) } upgradeUiState.selectedIndex = i; + // 多选强化先标记卡片,达到本次可选数量后再统一确认。 if (upgradeUiState.picksRemaining > 1) { bool currentlyMarked = upgradeUiState.marked[i]; @@ -216,6 +238,10 @@ static bool HandleUpgradeClick(HWND hWnd, int mouseX, int mouseY) /** * @brief 处理暂停和结束覆盖层点击。 + * @param hWnd 当前窗口句柄。 + * @param mouseX 鼠标横坐标。 + * @param mouseY 鼠标纵坐标。 + * @return 点击命中覆盖层按钮返回 true,否则返回 false。 */ static bool HandleOverlayClick(HWND hWnd, int mouseX, int mouseY) { @@ -286,6 +312,9 @@ static bool HandleOverlayClick(HWND hWnd, int mouseX, int mouseY) /** * @brief 处理鼠标左键释放事件,返回是否已处理。 + * @param hWnd 当前窗口句柄。 + * @param lParam 鼠标消息坐标参数。 + * @return 事件已被界面逻辑消费返回 true,否则返回 false。 */ bool HandleMouseClick(HWND hWnd, LPARAM lParam) { @@ -316,6 +345,8 @@ bool HandleMouseClick(HWND hWnd, LPARAM lParam) /** * @brief 处理鼠标滚轮事件。 + * @param hWnd 当前窗口句柄。 + * @param wParam 鼠标滚轮消息参数。 */ void HandleMouseWheel(HWND hWnd, WPARAM wParam) { @@ -337,6 +368,9 @@ void HandleMouseWheel(HWND hWnd, WPARAM wParam) /** * @brief 处理主菜单键盘导航。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + * @return 当前界面是菜单时返回 true,否则返回 false。 */ static bool HandleMenuKey(HWND hWnd, WPARAM key) { @@ -385,6 +419,9 @@ static bool HandleMenuKey(HWND hWnd, WPARAM key) /** * @brief 处理帮助和致谢页键盘导航。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + * @return 当前界面是帮助页时返回 true,否则返回 false。 */ static bool HandleRulesKey(HWND hWnd, WPARAM key) { @@ -514,6 +551,9 @@ static bool HandleRulesKey(HWND hWnd, WPARAM key) /** * @brief 处理升级选择界面键盘导航。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 + * @return 当前界面是升级选择时返回 true,否则返回 false。 */ static bool HandleUpgradeKey(HWND hWnd, WPARAM key) { @@ -626,6 +666,8 @@ static bool HandleUpgradeKey(HWND hWnd, WPARAM key) /** * @brief 处理游戏过程中的按键。 + * @param hWnd 当前窗口句柄。 + * @param key 按键虚拟键码。 */ static void HandlePlayingKey(HWND hWnd, WPARAM key) { @@ -709,6 +751,7 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) return; } + // 正常游玩按键先改变方块或触发技能,再统一刷新预测落点和界面。 switch (key) { case VK_LEFT: @@ -783,6 +826,8 @@ static void HandlePlayingKey(HWND hWnd, WPARAM key) /** * @brief 处理键盘按键事件。 + * @param hWnd 当前窗口句柄。 + * @param wParam 按键虚拟键码。 */ void HandleKeyDown(HWND hWnd, WPARAM wParam) { diff --git a/src/source/app/TetrisLayout.cpp b/src/source/app/TetrisLayout.cpp index 7027e43..df13d92 100644 --- a/src/source/app/TetrisLayout.cpp +++ b/src/source/app/TetrisLayout.cpp @@ -1,8 +1,15 @@ #include "stdafx.h" +/** + * @file TetrisLayout.cpp + * @brief 实现窗口缩放布局和各类按钮、卡片、列表项的点击区域计算。 + */ + #include "TetrisAppInternal.h" /** * @brief 将指定滚动偏移按步长调整,并限制在非负范围内。 + * @param scrollOffset 需要修改的滚动偏移。 + * @param delta 本次滚动增量。 */ void AdjustScrollOffset(int& scrollOffset, int delta) { @@ -19,6 +26,9 @@ void AdjustScrollOffset(int& scrollOffset, int delta) /** * @brief 按当前窗口缩放返回一次滚动操作的像素距离。 + * @param hWnd 当前窗口句柄。 + * @param baseStep 设计稿中的基础滚动步长。 + * @return 缩放后的滚动步长。 */ int GetScrollStep(HWND hWnd, int baseStep) { @@ -28,6 +38,8 @@ int GetScrollStep(HWND hWnd, int baseStep) /** * @brief 根据当前窗口大小计算整体界面缩放与偏移。 + * @param hWnd 当前窗口句柄。 + * @return 布局缩放、偏移和网格尺寸。 */ LayoutMetrics GetLayoutMetrics(HWND hWnd) { @@ -56,6 +68,9 @@ LayoutMetrics GetLayoutMetrics(HWND hWnd) /** * @brief 按当前布局比例缩放一个尺寸值。 + * @param metrics 当前布局参数。 + * @param value 设计稿尺寸值。 + * @return 缩放后的像素尺寸。 */ int ScaleValue(const LayoutMetrics& metrics, int value) { @@ -64,6 +79,9 @@ int ScaleValue(const LayoutMetrics& metrics, int value) /** * @brief 按当前布局比例缩放横坐标并叠加窗口偏移。 + * @param metrics 当前布局参数。 + * @param value 设计稿横坐标。 + * @return 实际窗口横坐标。 */ int ScaleXValue(const LayoutMetrics& metrics, int value) { @@ -72,12 +90,20 @@ int ScaleXValue(const LayoutMetrics& metrics, int value) /** * @brief 按当前布局比例缩放纵坐标并叠加窗口偏移。 + * @param metrics 当前布局参数。 + * @param value 设计稿纵坐标。 + * @return 实际窗口纵坐标。 */ int ScaleYValue(const LayoutMetrics& metrics, int value) { return metrics.offsetY + MulDiv(value, metrics.scale, 1000); } +/** + * @brief 获取主菜单中央卡片区域。 + * @param hWnd 当前窗口句柄。 + * @return 菜单卡片在窗口中的矩形区域。 + */ static RECT GetMenuCardRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -91,6 +117,12 @@ static RECT GetMenuCardRect(HWND hWnd) return rect; } +/** + * @brief 获取主菜单选项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 菜单选项序号。 + * @return 选项在窗口中的矩形区域。 + */ RECT GetMenuOptionRect(HWND hWnd, int index) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -106,6 +138,11 @@ RECT GetMenuOptionRect(HWND hWnd, int index) return rect; } +/** + * @brief 获取帮助页卡片区域。 + * @param hWnd 当前窗口句柄。 + * @return 帮助卡片在窗口中的矩形区域。 + */ static RECT GetRulesCardRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -119,6 +156,12 @@ static RECT GetRulesCardRect(HWND hWnd) return rect; } +/** + * @brief 获取帮助页首页选项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 帮助选项序号。 + * @return 选项在窗口中的矩形区域。 + */ RECT GetHelpOptionRect(HWND hWnd, int index) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -143,6 +186,12 @@ RECT GetHelpOptionRect(HWND hWnd, int index) return rect; } +/** + * @brief 获取技能演示列表项的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 技能演示条目序号。 + * @return 条目在窗口中的矩形区域。 + */ RECT GetHelpSkillDemoItemRect(HWND hWnd, int index) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -167,6 +216,11 @@ RECT GetHelpSkillDemoItemRect(HWND hWnd, int index) return rect; } +/** + * @brief 获取帮助页底部返回提示的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 返回提示在窗口中的矩形区域。 + */ RECT GetHelpBackHintRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -181,6 +235,12 @@ RECT GetHelpBackHintRect(HWND hWnd) return rect; } +/** + * @brief 获取致谢页左右箭头按钮区域。 + * @param hWnd 当前窗口句柄。 + * @param direction 小于 0 为左箭头,大于 0 为右箭头。 + * @return 箭头按钮在窗口中的矩形区域。 + */ RECT GetCreditArrowRect(HWND hWnd, int direction) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -201,6 +261,11 @@ RECT GetCreditArrowRect(HWND hWnd, int direction) return rect; } +/** + * @brief 获取升级选择覆盖层区域。 + * @param hWnd 当前窗口句柄。 + * @return 覆盖层在窗口中的矩形区域。 + */ static RECT GetUpgradeOverlayRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -214,6 +279,12 @@ static RECT GetUpgradeOverlayRect(HWND hWnd) return rect; } +/** + * @brief 获取升级选择卡片的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 强化卡片序号。 + * @return 卡片在窗口中的矩形区域。 + */ RECT GetUpgradeCardRect(HWND hWnd, int index) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -242,6 +313,11 @@ RECT GetUpgradeCardRect(HWND hWnd, int index) return rect; } +/** + * @brief 获取暂停或结束提示覆盖层区域。 + * @param hWnd 当前窗口句柄。 + * @return 覆盖层在窗口中的矩形区域。 + */ static RECT GetGameOverlayRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -260,6 +336,13 @@ static RECT GetGameOverlayRect(HWND hWnd) return rect; } +/** + * @brief 获取暂停或结束覆盖层按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @param index 按钮序号。 + * @param buttonCount 当前覆盖层按钮总数。 + * @return 按钮在窗口中的矩形区域。 + */ RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -274,6 +357,11 @@ RECT GetOverlayButtonRect(HWND hWnd, int index, int buttonCount) return rect; } +/** + * @brief 获取左上角返回按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 返回按钮在窗口中的矩形区域。 + */ RECT GetBackButtonRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -287,6 +375,11 @@ RECT GetBackButtonRect(HWND hWnd) return rect; } +/** + * @brief 获取右下角音乐按钮的点击区域。 + * @param hWnd 当前窗口句柄。 + * @return 音乐按钮在窗口中的矩形区域。 + */ RECT GetMusicButtonRect(HWND hWnd) { LayoutMetrics metrics = GetLayoutMetrics(hWnd); @@ -316,6 +409,13 @@ RECT GetMusicButtonRect(HWND hWnd) return buttonRect; } +/** + * @brief 判断点坐标是否落在矩形内部。 + * @param rect 待判断矩形。 + * @param x 点的横坐标。 + * @param y 点的纵坐标。 + * @return 点在矩形内返回 true,否则返回 false。 + */ bool IsPointInRect(const RECT& rect, int x, int y) { return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom; diff --git a/src/source/app/TetrisMedia.cpp b/src/source/app/TetrisMedia.cpp index becc922..006feda 100644 --- a/src/source/app/TetrisMedia.cpp +++ b/src/source/app/TetrisMedia.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisMedia.cpp + * @brief 实现背景音乐开关和复活视频播放逻辑。 + */ + #include "Tetris.h" #include "TetrisAppInternal.h" #include "TetrisAssets.h" @@ -12,6 +17,9 @@ static constexpr const wchar_t* kReviveVideoAlias = L"TereisReviveVideo"; /** * @brief 尝试通过 MCI 循环播放指定音乐文件。 + * @param path 音频文件路径。 + * @param forceMpegVideo 是否强制按 mpegvideo 类型打开。 + * @return 播放成功返回 true,否则返回 false。 */ static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo) { @@ -22,6 +30,7 @@ static bool TryPlayMciLoop(const std::wstring& path, bool forceMpegVideo) mciSendStringW((std::wstring(L"close ") + kBgmAlias).c_str(), nullptr, 0, nullptr); + // MCI 对部分 OGG/视频容器识别不稳定,调用方会按不同类型尝试。 std::wstring openCommand = L"open \"" + path + L"\" "; if (forceMpegVideo) { @@ -132,6 +141,7 @@ void StartBackgroundMusic() /** * @brief 切换背景音乐开关并刷新窗口。 + * @param hWnd 当前窗口句柄。 */ void ToggleBackgroundMusic(HWND hWnd) { @@ -149,6 +159,8 @@ void ToggleBackgroundMusic(HWND hWnd) /** * @brief 播放复活视频,先尝试 MCI,全屏播放失败时退回系统默认播放器。 + * @param hWnd 当前窗口句柄,用作 MCI 父窗口和 ShellExecute 父窗口。 + * @return 播放成功返回 true,否则返回 false。 */ bool PlayReviveVideo(HWND hWnd) { @@ -176,6 +188,7 @@ bool PlayReviveVideo(HWND hWnd) StopBackgroundMusic(); } + // 先用 MCI 全屏同步播放;失败时再交给系统默认播放器。 bool played = false; for (int attempt = 0; attempt < 2 && !played; attempt++) { diff --git a/src/source/app/TetrisTimers.cpp b/src/source/app/TetrisTimers.cpp index 86281ee..d0e653f 100644 --- a/src/source/app/TetrisTimers.cpp +++ b/src/source/app/TetrisTimers.cpp @@ -1,10 +1,16 @@ #include "stdafx.h" +/** + * @file TetrisTimers.cpp + * @brief 实现游戏下落、视觉特效、致谢动画和 Rogue 限时状态的定时推进。 + */ + #include "TetrisAppInternal.h" static MMRESULT creditTimerHandle = 0; /** * @brief 多媒体定时器回调,用于高频率请求致谢页动画刷新。 + * @param userData 创建定时器时传入的窗口句柄。 */ static void CALLBACK CreditTimerCallback(UINT, UINT, DWORD_PTR userData, DWORD_PTR, DWORD_PTR) { @@ -17,6 +23,7 @@ static void CALLBACK CreditTimerCallback(UINT, UINT, DWORD_PTR userData, DWORD_P /** * @brief 重置主下落定时器。 + * @param hWnd 当前窗口句柄。 */ void ResetGameTimer(HWND hWnd) { @@ -26,6 +33,7 @@ void ResetGameTimer(HWND hWnd) /** * @brief 启动游戏、特效和致谢页动画定时器。 + * @param hWnd 当前窗口句柄。 */ void StartAppTimers(HWND hWnd) { @@ -39,12 +47,14 @@ void StartAppTimers(HWND hWnd) TIME_PERIODIC | TIME_CALLBACK_FUNCTION); if (creditTimerHandle == 0) { + // 多媒体定时器不可用时退回普通窗口定时器,保证致谢页仍可动画。 SetTimer(hWnd, CREDIT_TIMER_ID, CREDIT_TIMER_INTERVAL, nullptr); } } /** * @brief 停止游戏、特效和致谢页动画定时器。 + * @param hWnd 当前窗口句柄。 */ void StopAppTimers(HWND hWnd) { @@ -63,6 +73,7 @@ void StopAppTimers(HWND hWnd) /** * @brief 处理致谢页高频动画刷新消息。 + * @param hWnd 当前窗口句柄。 */ void HandleCreditTick(HWND hWnd) { @@ -74,6 +85,8 @@ void HandleCreditTick(HWND hWnd) /** * @brief 推进 Rogue 限时状态并按需要重置下落定时器。 + * @param hWnd 当前窗口句柄。 + * @return 任意状态变化需要刷新界面时返回 true。 */ static bool TickRogueTimedStates(HWND hWnd) { @@ -121,6 +134,8 @@ static bool TickRogueTimedStates(HWND hWnd) /** * @brief 检查极限玩家的危险等级计时。 + * @param hWnd 当前窗口句柄。 + * @return 危险等级变化时返回 true。 */ static bool TickExtremeDanger(HWND hWnd) { @@ -156,6 +171,8 @@ static bool TickExtremeDanger(HWND hWnd) /** * @brief 检查高堆叠触发的时间缓流。 + * @param hWnd 当前窗口句柄。 + * @return 成功触发时间缓流时返回 true。 */ static bool TryStartTimeDilation(HWND hWnd) { @@ -204,6 +221,8 @@ static bool TryStartTimeDilation(HWND hWnd) /** * @brief 推进一次自动下落逻辑。 + * @param hWnd 当前窗口句柄。 + * @return 游戏状态推进后需要刷新界面返回 true。 */ static bool TickGameFall(HWND hWnd) { @@ -248,6 +267,8 @@ static bool TickGameFall(HWND hWnd) /** * @brief 处理窗口定时器消息。 + * @param hWnd 当前窗口句柄。 + * @param timerId 触发的定时器编号。 */ void HandleTimerMessage(HWND hWnd, WPARAM timerId) { diff --git a/src/source/common/TetrisAssets.cpp b/src/source/common/TetrisAssets.cpp index 1ab196f..bffa11d 100644 --- a/src/source/common/TetrisAssets.cpp +++ b/src/source/common/TetrisAssets.cpp @@ -1,8 +1,19 @@ #include "stdafx.h" +/** + * @file TetrisAssets.cpp + * @brief 实现资源路径解析和文件存在性检查,支持构建目录或项目根目录运行。 + */ + #include "TetrisAssets.h" /** * @brief 根据程序所在目录拼出项目资源文件的绝对路径。 + * + * 构建脚本会把可执行文件放到构建目录,因此这里先回到项目根目录, + * 再拼接 assets 下的图片、音频或视频路径。 + * + * @param relativePath 相对于项目根目录的资源路径。 + * @return 规范化后的绝对路径;解析失败时返回拼接路径。 */ std::wstring BuildAssetPath(const wchar_t* relativePath) { @@ -16,6 +27,7 @@ std::wstring BuildAssetPath(const wchar_t* relativePath) basePath.resize(lastSlash); } + // 可执行文件位于构建目录,向上两级回到项目根目录。 std::wstring projectRelative = basePath + L"\\..\\..\\" + relativePath; wchar_t fullPath[MAX_PATH] = {}; DWORD result = GetFullPathNameW(projectRelative.c_str(), MAX_PATH, fullPath, nullptr); @@ -29,6 +41,11 @@ std::wstring BuildAssetPath(const wchar_t* relativePath) /** * @brief 根据当前工作目录拼出项目资源文件的绝对路径。 + * + * 这个路径用于从 IDE 或命令行直接以项目根目录运行时查找资源。 + * + * @param relativePath 相对于当前工作目录的资源路径。 + * @return 规范化后的绝对路径;解析失败时返回拼接路径。 */ std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath) { @@ -52,6 +69,8 @@ std::wstring BuildWorkingDirAssetPath(const wchar_t* relativePath) /** * @brief 判断指定路径是否存在且不是目录。 + * @param path 待检查的文件路径。 + * @return 文件存在且不是目录返回 true,否则返回 false。 */ bool FileExists(const std::wstring& path) { diff --git a/src/source/extensions/TetrisGameExtensions.cpp b/src/source/extensions/TetrisGameExtensions.cpp index 0816a73..f8bdd00 100644 --- a/src/source/extensions/TetrisGameExtensions.cpp +++ b/src/source/extensions/TetrisGameExtensions.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisGameExtensions.cpp + * @brief 实现玩家统计、视觉特效、模式切换、复活和帮助/致谢页面状态管理。 + */ + #include "TetrisLogicInternal.h" int pendingLineClearEffectTicks = 0; @@ -8,6 +13,8 @@ int pendingLineClearEffectLineCount = 0; /** * @brief 重置经典或 Rogue 模式使用的玩家统计数据。 + * @param stats 需要重置的统计结构。 + * @param useRogueRules 是否按 Rogue 模式设置初始经验需求。 */ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) { @@ -85,6 +92,9 @@ void ResetPlayerStats(PlayerStats& stats, bool useRogueRules) /** * @brief 设置界面右侧显示的即时反馈标题、内容和持续时间。 + * @param title 反馈标题。 + * @param detail 反馈详情。 + * @param ticks 显示持续的游戏计时次数。 */ void SetFeedbackMessage(const TCHAR* title, const TCHAR* detail, int ticks) { @@ -125,6 +135,7 @@ void ResetVisualEffects() /** * @brief 推进视觉效果计时,并返回是否仍有动画需要刷新。 + * @return 仍有动画需要刷新返回 true,否则返回 false。 */ bool TickVisualEffects() { @@ -177,6 +188,7 @@ bool TickVisualEffects() /** * @brief 推进致谢页左右切换动画,并返回是否需要刷新界面。 + * @return 需要刷新界面返回 true,否则返回 false。 */ bool TickCreditAnimation() { @@ -191,6 +203,10 @@ bool TickCreditAnimation() /** * @brief 添加一段棋盘坐标系中的浮动文字效果。 + * @param boardX 棋盘内部横坐标,使用 100 为一格的坐标系。 + * @param boardY 棋盘内部纵坐标,使用 100 为一格的坐标系。 + * @param text 浮动文字内容。 + * @param color 文字颜色。 */ static void AddFloatingText(int boardX, int boardY, const TCHAR* text, COLORREF color) { @@ -211,6 +227,12 @@ static void AddFloatingText(int boardX, int boardY, const TCHAR* text, COLORREF /** * @brief 添加一个棋盘坐标系中的粒子效果。 + * @param boardX 粒子起始横坐标。 + * @param boardY 粒子起始纵坐标。 + * @param velocityX 横向速度。 + * @param velocityY 纵向速度。 + * @param size 粒子尺寸。 + * @param color 粒子颜色。 */ static void AddParticle(int boardX, int boardY, int velocityX, int velocityY, int size, COLORREF color) { @@ -233,6 +255,10 @@ static void AddParticle(int boardX, int boardY, int velocityX, int velocityY, in /** * @brief 在指定棋盘坐标周围生成一组爆裂粒子。 + * @param boardX 爆裂中心横坐标。 + * @param boardY 爆裂中心纵坐标。 + * @param baseColor 主粒子颜色。 + * @param strongBurst 是否使用更强的粒子数量和速度。 */ static void AddBurstParticles(int boardX, int boardY, COLORREF baseColor, bool strongBurst) { @@ -296,6 +322,10 @@ static void AddBurstParticles(int boardX, int boardY, COLORREF baseColor, bool s /** * @brief 添加一个被清除格子的短时高亮效果。 + * @param x 棋盘列号。 + * @param y 棋盘行号。 + * @param color 高亮颜色。 + * @param strongFlash 是否使用更长的强高亮。 */ static void AddCellFlash(int x, int y, COLORREF color, bool strongFlash) { @@ -315,6 +345,9 @@ static void AddCellFlash(int x, int y, COLORREF color, bool strongFlash) /** * @brief 暂存消行动画,等待升级选择结束后再播放。 + * @param rows 被消除的行号数组。 + * @param rowCount 行号数量。 + * @param linesCleared 实际消除行数。 */ void QueueLineClearEffect(const int* rows, int rowCount, int linesCleared) { @@ -358,6 +391,9 @@ void PlayPendingLineClearEffect() /** * @brief 触发标准消行动画和浮动文字。 + * @param rows 被消除的行号数组。 + * @param rowCount 行号数量。 + * @param linesCleared 实际消除行数。 */ void TriggerLineClearEffect(const int* rows, int rowCount, int linesCleared) { @@ -414,6 +450,9 @@ void TriggerLineClearEffect(const int* rows, int rowCount, int linesCleared) /** * @brief 为指定棋盘格集合触发清除粒子效果。 + * @param cells 被清除格子数组。 + * @param cellCount 格子数量。 + * @param strongBurst 是否使用更强的粒子爆裂效果。 */ void TriggerCellClearEffect(const Point* cells, int cellCount, bool strongBurst) { @@ -422,6 +461,10 @@ void TriggerCellClearEffect(const Point* cells, int cellCount, bool strongBurst) /** * @brief 为指定棋盘格集合触发带颜色区分的清除高亮和粒子效果。 + * @param cells 被清除格子数组。 + * @param cellCount 格子数量。 + * @param flashColor 高亮颜色。 + * @param strongBurst 是否使用更强的粒子爆裂效果。 */ void TriggerColoredCellClearEffect(const Point* cells, int cellCount, COLORREF flashColor, bool strongBurst) { @@ -445,6 +488,10 @@ void TriggerColoredCellClearEffect(const Point* cells, int cellCount, COLORREF f /** * @brief 为一个受重力下落的固定方块记录纵向残影和落点粒子。 + * @param x 棋盘列号。 + * @param fromY 起始行号。 + * @param toY 目标行号。 + * @param cellValue 方块格子值。 */ void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue) { @@ -490,6 +537,10 @@ void TriggerGravityFallEffect(int x, int fromY, int toY, int cellValue) /** * @brief 判断指定方块、旋转状态和位置是否可以合法放置。 + * @param pieceType 方块类型编号。 + * @param pieceState 方块旋转状态。 + * @param position 待检测的左上角坐标。 + * @return 可以放置返回 true,否则返回 false。 */ bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) { @@ -522,6 +573,9 @@ bool IsPiecePlacementValid(int pieceType, int pieceState, Point position) /** * @brief 尝试把旋转后的方块横向偏移指定格数后放置。 + * @param nextState 旋转后的状态编号。 + * @param offsetX 横向试探偏移。 + * @return 偏移后可以放置返回 true,否则返回 false。 */ bool TryRotateWithOffset(int nextState, int offsetX) { @@ -574,6 +628,7 @@ void ReviveAfterVideo() /** * @brief 按指定模式开始新游戏。 + * @param mode 游戏模式,取值来自 GameMode。 */ void StartGameWithMode(int mode) { @@ -632,7 +687,7 @@ void OpenRulesScreen() } /** - * @brief 打开致谢界面并重置致谢页切换状态。 + * @brief 打开 Rogue 技能演示选择页并重置帮助页状态。 */ void OpenSkillDemoScreen() { @@ -648,6 +703,9 @@ void OpenSkillDemoScreen() creditAnimationDirection = 0; } +/** + * @brief 打开致谢界面并重置致谢页切换状态。 + */ void OpenCreditScreen() { rogueDemoMode = false; @@ -664,6 +722,7 @@ void OpenCreditScreen() /** * @brief 切换致谢页图片,并启动左右滑动动画。 + * @param direction 小于 0 向前切换,大于 0 向后切换。 */ void ChangeCreditPage(int direction) { diff --git a/src/source/logic/TetrisPieceEffects.cpp b/src/source/logic/TetrisPieceEffects.cpp index 72aee6a..cf1de84 100644 --- a/src/source/logic/TetrisPieceEffects.cpp +++ b/src/source/logic/TetrisPieceEffects.cpp @@ -1,8 +1,16 @@ #include "stdafx.h" +/** + * @file TetrisPieceEffects.cpp + * @brief 实现彩虹、爆破、激光、十字和稳定结构等特殊方块落地效果。 + */ + #include "TetrisLogicInternal.h" /** * @brief 结算彩虹方块固定后的染色和清除效果。 + * @param overflowTop 固定时是否已经越过棋盘顶部。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 */ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fixedCellCount) { @@ -11,6 +19,7 @@ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fi return; } + // 优先使用实际固定格子的平均行作为主色行,避免旋转形状偏移导致判定不自然。 int rainbowAnchorRow = point.y + 1; if (fixedCellCount > 0) { @@ -72,6 +81,8 @@ void ApplyRainbowLandingEffect(bool overflowTop, const Point* fixedCells, int fi /** * @brief 结算爆破方块的范围清除效果。 + * @param explosiveCells 爆破方块写入棋盘的格子数组。 + * @param explosiveCellCount 爆破格子数量。 */ static void ApplyExplosiveLandingEffect(const Point* explosiveCells, int explosiveCellCount) { @@ -102,6 +113,7 @@ static void ApplyExplosiveLandingEffect(const Point* explosiveCells, int explosi explosiveExpGain); SetFeedbackMessage(_T("爆破核心"), explosiveDetail, 12); + // 连环炸弹需要等标准消行判断完成后,再决定是否追加一次小爆炸。 if (rogueStats.chainBombLevel > 0 && explosiveCellCount > 0) { pendingChainBombCenter = explosiveCells[0]; @@ -111,6 +123,8 @@ static void ApplyExplosiveLandingEffect(const Point* explosiveCells, int explosi /** * @brief 结算激光方块的整列清除效果。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 */ static void ApplyLaserLandingEffect(const Point* fixedCells, int fixedCellCount) { @@ -153,6 +167,8 @@ static void ApplyLaserLandingEffect(const Point* fixedCells, int fixedCellCount) /** * @brief 结算十字方块的整行整列清除效果。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 */ static void ApplyCrossLandingEffect(const Point* fixedCells, int fixedCellCount) { @@ -225,6 +241,10 @@ static void ApplyStableStructureEffect() /** * @brief 结算爆破、激光、十字和稳定结构等特殊落地效果。 + * @param fixedCells 当前方块写入棋盘的格子数组。 + * @param fixedCellCount 写入棋盘的格子数量。 + * @param explosiveCells 爆破方块写入棋盘的格子数组。 + * @param explosiveCellCount 爆破格子数量。 */ void ApplySpecialLandingEffects(const Point* fixedCells, int fixedCellCount, const Point* explosiveCells, int explosiveCellCount) { diff --git a/src/source/render/TetrisRenderAssets.cpp b/src/source/render/TetrisRenderAssets.cpp index 8d42f7f..8d4a989 100644 --- a/src/source/render/TetrisRenderAssets.cpp +++ b/src/source/render/TetrisRenderAssets.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisRenderAssets.cpp + * @brief 实现 GDI+ 初始化以及背景图、致谢页图片的加载与缓存。 + */ + #include "TetrisRenderInternal.h" #include "TetrisAssets.h" #include @@ -10,6 +15,8 @@ using namespace Gdiplus; /** * @brief 尝试从指定路径加载 GDI+ 位图。 + * @param path 图片文件路径。 + * @return 加载成功返回位图指针,失败返回 nullptr。 */ static Bitmap* TryLoadBitmap(const std::wstring& path) { @@ -30,6 +37,7 @@ static Bitmap* TryLoadBitmap(const std::wstring& path) /** * @brief 确保 GDI+ 已初始化,返回初始化是否成功。 + * @return GDI+ 可用返回 true,否则返回 false。 */ static bool EnsureGdiplusStarted() { @@ -39,6 +47,7 @@ static bool EnsureGdiplusStarted() if (!attempted) { + // GDI+ 只需要初始化一次,静态标记避免重复启动。 attempted = true; GdiplusStartupInput startupInput; started = GdiplusStartup(&gdiplusToken, &startupInput, nullptr) == Ok; @@ -49,6 +58,7 @@ static bool EnsureGdiplusStarted() /** * @brief 加载并缓存主背景图片。 + * @return 成功时返回缓存位图指针,失败时返回 nullptr。 */ Bitmap* LoadBackgroundImage() { @@ -69,6 +79,7 @@ Bitmap* LoadBackgroundImage() BuildWorkingDirAssetPath(L"assets\\images\\background.bmp") }; + // 同时支持构建目录运行和项目根目录运行两种启动方式。 for (const std::wstring& candidate : candidates) { backgroundImage = TryLoadBitmap(candidate); @@ -85,6 +96,8 @@ Bitmap* LoadBackgroundImage() /** * @brief 按序号加载并缓存致谢页图片。 + * @param index 致谢页图片序号。 + * @return 成功时返回缓存位图指针,失败或越界时返回 nullptr。 */ Bitmap* LoadCreditImage(int index) { @@ -123,6 +136,7 @@ Bitmap* LoadCreditImage(int index) }; int candidateCount = (index == 3) ? 8 : 2; + // 第四张致谢图历史上有多种扩展名,这里保留兼容查找。 for (int i = 0; i < candidateCount; i++) { creditImages[index] = TryLoadBitmap(creditExtraCandidates[i]); diff --git a/src/source/rogue/TetrisRogue.cpp b/src/source/rogue/TetrisRogue.cpp index 4dc22cc..9b257b9 100644 --- a/src/source/rogue/TetrisRogue.cpp +++ b/src/source/rogue/TetrisRogue.cpp @@ -1,4 +1,9 @@ #include "stdafx.h" +/** + * @file TetrisRogue.cpp + * @brief 实现 Rogue 模式的强化池、等级成长、特殊方块、主动技能、难度和技能演示。 + */ + #include "TetrisLogicInternal.h" /** @@ -1513,6 +1518,8 @@ static int GetRogueExpByLines(int linesCleared) /** * @brief 结算经验条并返回本次连续升级次数。 + * @param stats 需要结算经验的玩家统计。 + * @return 本次触发的升级次数。 */ static int ApplyLevelProgress(PlayerStats& stats) { @@ -1531,6 +1538,8 @@ static int ApplyLevelProgress(PlayerStats& stats) /** * @brief 触发升级冲击波,清除底部指定数量的行。 + * @param rowsToClear 需要清除的底部行数。 + * @return 实际清除的行数。 */ static int TriggerUpgradeShockwave(int rowsToClear) { @@ -1547,6 +1556,9 @@ static int TriggerUpgradeShockwave(int rowsToClear) /** * @brief 按权重和当前局势生成升级菜单中的候选强化。 + * + * 该函数只负责生成 UI 选项,不应用强化效果。候选池会先过滤前置、 + * 等级上限和互斥条件,再按动态权重不放回抽取,保证同一轮不会重复出现。 */ static void FillUpgradeOptions() { @@ -1554,6 +1566,7 @@ static void FillUpgradeOptions() int selectableWeights[kUpgradePoolSize] = { 0 }; int selectableCount = 0; + // 第一段:筛出当前真正可选的强化,并记录它们的动态权重。 for (int i = 0; i < kUpgradePoolSize; i++) { if (IsUpgradeSelectable(kUpgradePool[i])) @@ -1564,6 +1577,7 @@ static void FillUpgradeOptions() } } + // 命运轮盘把候选扩到 6 个;双重抉择和命运轮盘都会允许本轮选 2 个。 int optionLimit = (rogueStats.destinyWheelLevel > 0) ? 6 : 3; int optionCount = selectableCount < optionLimit ? selectableCount : optionLimit; upgradeUiState.optionCount = optionCount; @@ -1575,6 +1589,7 @@ static void FillUpgradeOptions() upgradeUiState.marked[i] = false; } + // 第二段:按权重不放回抽取候选,抽中后用末尾元素覆盖当前槽位。 for (int i = 0; i < optionCount; i++) { int totalWeight = 0; @@ -1612,6 +1627,7 @@ static void FillUpgradeOptions() upgradeUiState.options[i].category = pickedEntry.category; upgradeUiState.options[i].description = pickedEntry.description; + // 方块改造目前固定展示 I 块概率提升,说明文字需要运行时拼接。 if (pickedEntry.id == UPGRADE_PIECE_TUNING) { int targetPieceType = 0; @@ -1632,6 +1648,7 @@ static void FillUpgradeOptions() selectableCount--; } + // 命运轮盘在候选中随机附加一个诅咒,确认时才提高下一次升级需求。 if (rogueStats.destinyWheelLevel > 0 && optionCount > 0) { int cursedIndex = rand() % optionCount; @@ -1641,6 +1658,7 @@ static void FillUpgradeOptions() /** * @brief 综合难度、强化和临时状态计算 Rogue 模式下落间隔。 + * @return 当前应使用的下落计时器间隔,单位毫秒。 */ int GetRogueFallInterval() { @@ -1692,14 +1710,24 @@ int GetRogueFallInterval() /** * @brief 根据强化编号把对应效果写入 Rogue 属性。 + * + * 该函数集中修改 Rogue 属性和少量即时棋盘效果。调用者已经完成赌徒契约、 + * 多选确认和诅咒处理,因此这里保持强化效果本身的顺序和数值不变。 + * + * @param upgradeId 强化编号。 + * @param targetPieceType 方块改造目标方块类型;当前实现固定使用 I 块。 + * @param applyCount 本次效果应用次数,赌徒落空时可能为 0。 */ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) { + (void)targetPieceType; + if (applyCount <= 0) { return; } + // 基础成长类强化直接叠加倍率或层数。 switch (upgradeId) { case UPGRADE_SCORE_MULTIPLIER: @@ -1738,6 +1766,7 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) break; case UPGRADE_PRESSURE_RELIEF: { + // 卸压清场立即从最高占用行开始删除,直接改善当前局面。 rogueStats.pressureReliefLevel += applyCount; for (int i = 0; i < applyCount; i++) { @@ -1838,6 +1867,7 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) rogueStats.previewUpgradeLevel = rogueStats.previewCount - 1; break; case UPGRADE_BLOCK_STORM: + // 方块风暴会同时改写预览队列,确保玩家马上看到连续 I 块。 rogueStats.blockStormLevel = 1; rogueStats.blockStormPiecesRemaining = 5; nextTypes[0] = 0; @@ -1886,6 +1916,10 @@ static void ApplyUpgradeById(int upgradeId, int targetPieceType, int applyCount) /** * @brief 为技能清除的格子结算得分和经验奖励。 + * @param clearedCells 清除格子数。 + * @param scoreGain 返回本次得分增量。 + * @param expGain 返回本次经验增量。 + * @param allowLevelProgress 是否允许本次奖励触发升级流程。 */ void AwardRogueSkillClearRewards(int clearedCells, int& scoreGain, int& expGain, bool allowLevelProgress) { @@ -1968,6 +2002,7 @@ void CheckRogueLevelProgress() upgradeUiState.pendingCount += levelUps; + // 升级冲击波和进化冲击延后到升级菜单关闭后播放,避免菜单遮挡反馈。 int shockwaveRows = 0; if (rogueStats.evolutionImpactLevel > 0) { @@ -2033,6 +2068,7 @@ void ApplyBoardGravity() /** * @brief 结算一次标准消行带来的 Rogue 得分、经验、连击和派生效果。 + * @param linesCleared 本次标准消行数量。 */ void ApplyLineClearResult(int linesCleared) { @@ -2053,6 +2089,7 @@ void ApplyLineClearResult(int linesCleared) return; } + // 基础收益先计算,再依次套用风险、成长、狂热、赌徒和连击类修正。 int scoreGain = GetRogueScoreByLines(linesCleared); scoreGain = scoreGain * rogueStats.scoreMultiplierPercent / 100; @@ -2438,6 +2475,9 @@ void OpenUpgradeMenu() /** * @brief 确认升级菜单中的选择并应用对应强化效果。 + * + * 多选流程会先检查标记数量,再按卡片顺序逐个应用;单选流程应用后会移除 + * 当前卡片。所有待升级次数结算完成后,才恢复游戏、播放暂存消行动画和冲击波。 */ void ConfirmUpgradeSelection() { @@ -2446,6 +2486,7 @@ void ConfirmUpgradeSelection() return; } + // 命运轮盘或双重抉择使用多选分支,避免选完第一张后候选池被立即刷新。 if (upgradeUiState.picksRemaining > 1) { if (upgradeUiState.markedCount != upgradeUiState.picksRemaining) @@ -2522,6 +2563,7 @@ void ConfirmUpgradeSelection() return; } + // 普通升级只应用当前高亮卡片,并把剩余卡片前移,支持后续 picksRemaining。 UpgradeOption selectedOption = upgradeUiState.options[upgradeUiState.selectedIndex]; TCHAR gamblerSuffix[64] = _T(""); int applyCount = RollGamblerApplyCount(gamblerSuffix, 64, false); @@ -2580,6 +2622,7 @@ void ConfirmUpgradeSelection() return; } + // 连续升级时重新生成下一轮强化;全部完成后回到游戏并播放延迟效果。 if (upgradeUiState.pendingCount > 0) { upgradeUiState.pendingCount--; diff --git a/src/source/stdafx.cpp b/src/source/stdafx.cpp index 25ea8d8..0e9d621 100644 --- a/src/source/stdafx.cpp +++ b/src/source/stdafx.cpp @@ -1,6 +1,7 @@ -// stdafx.cpp : 只包括标准包含文件的源文件 -// Tetris.pch 将作为预编译头 -// stdafx.obj 将包含预编译类型信息 +/** + * @file stdafx.cpp + * @brief 预编译头源文件,只包含 stdafx.h 以生成共享编译信息。 + */ #include "stdafx.h"