补强注释

This commit is contained in:
2026-04-28 23:18:51 +08:00
parent 0840a807b5
commit 1c000c3c21
21 changed files with 888 additions and 12 deletions
+81 -3
View File
@@ -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);
+10 -1
View File
@@ -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;
+28
View File
@@ -1,4 +1,9 @@
#include "stdafx.h"
/**
* @file TetrisRender.cpp
* @brief 实现主菜单、帮助页、游戏棋盘、侧栏、覆盖层和升级界面的完整绘制逻辑。
*/
#include "Tetris.h"
#include "TetrisRenderInternal.h"
#include <objidl.h>
@@ -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 =
+45
View File
@@ -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)
{
+100
View File
@@ -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;
+13
View File
@@ -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++)
{
+21
View File
@@ -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)
{
+19
View File
@@ -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)
{
+60 -1
View File
@@ -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)
{
+20
View File
@@ -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)
{
+14
View File
@@ -1,4 +1,9 @@
#include "stdafx.h"
/**
* @file TetrisRenderAssets.cpp
* @brief GDI+
*/
#include "TetrisRenderInternal.h"
#include "TetrisAssets.h"
#include <objidl.h>
@@ -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]);
+43
View File
@@ -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--;
+4 -3
View File
@@ -1,6 +1,7 @@
// stdafx.cpp : 只包括标准包含文件的源文件
// Tetris.pch 将作为预编译头
// stdafx.obj 将包含预编译类型信息
/**
* @file stdafx.cpp
* @brief stdafx.h
*/
#include "stdafx.h"