#include "stdafx.h" /** * @file Tetris.cpp * @brief 实现 Win32 程序入口、主窗口创建、消息分发和双缓冲绘制流程。 */ #include "Tetris.h" #include "TetrisAppInternal.h" #define MAX_LOADSTRING 100 HINSTANCE hInst; TCHAR szTitle[MAX_LOADSTRING]; TCHAR szWindowClass[MAX_LOADSTRING]; bool bgmEnabled = true; /** * @brief 注册主窗口类,供 CreateWindow 创建游戏窗口使用。 * @param hInstance 当前程序实例句柄。 * @return RegisterClassEx 返回的窗口类原子值。 */ ATOM MyRegisterClass(HINSTANCE hInstance); /** * @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, int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // 旧版工程模板没有自动声明 DPI 感知,这里动态获取函数避免系统不支持时报错。 HMODULE user32Module = GetModuleHandle(_T("user32.dll")); if (user32Module != nullptr) { typedef BOOL(WINAPI* SetProcessDPIAwareFunc)(); SetProcessDPIAwareFunc setProcessDPIAware = (SetProcessDPIAwareFunc)GetProcAddress(user32Module, "SetProcessDPIAware"); if (setProcessDPIAware != nullptr) { setProcessDPIAware(); } } LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_TETRIS, szWindowClass, MAX_LOADSTRING); lstrcpy(szTitle, _T("\u4fc4\u7f57\u65af\u65b9\u5757")); MyRegisterClass(hInstance); if (!InitInstance(hInstance, nCmdShow)) { return FALSE; } HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TETRIS)); MSG msg; // 标准消息循环负责把键盘、鼠标、计时器和绘制消息送入窗口过程。 while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int)msg.wParam; } /** * @brief 注册主窗口类,供 CreateWindow 创建游戏窗口使用。 * @param hInstance 当前程序实例句柄。 * @return RegisterClassEx 返回的窗口类原子值。 */ ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TETRIS)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = nullptr; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SMALL)); 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 }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW, FALSE); hInst = hInstance; HWND hWnd = CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, rect.right - rect.left, rect.bottom - rect.top, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); 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(); StartBackgroundMusic(); StartAppTimers(hWnd); InvalidateRect(hWnd, nullptr, FALSE); break; case WM_COMMAND: { int wmId = LOWORD(wParam); switch (wmId) { case IDM_SKILL_DEMO: OpenSkillDemoScreen(); InvalidateRect(hWnd, nullptr, FALSE); break; case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_CREDIT_TICK: HandleCreditTick(hWnd); break; case WM_TIMER: HandleTimerMessage(hWnd, wParam); break; case WM_SIZE: InvalidateRect(hWnd, nullptr, FALSE); break; case WM_LBUTTONUP: if (!HandleMouseClick(hWnd, lParam)) { return DefWindowProc(hWnd, message, wParam, lParam); } break; case WM_MOUSEWHEEL: HandleMouseWheel(hWnd, wParam); break; case WM_KEYDOWN: HandleKeyDown(hWnd, wParam); break; case WM_ERASEBKGND: return 1; case WM_PAINT: { // 使用内存 DC 双缓冲绘制,减少窗口缩放和动画刷新时的闪烁。 PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); RECT clientRect; GetClientRect(hWnd, &clientRect); HDC memDC = CreateCompatibleDC(hdc); HBITMAP memBitmap = CreateCompatibleBitmap( hdc, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top); HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap); TDrawScreen(memDC, hWnd); BitBlt( hdc, 0, 0, clientRect.right - clientRect.left, clientRect.bottom - clientRect.top, memDC, 0, 0, SRCCOPY); SelectObject(memDC, oldBitmap); DeleteObject(memBitmap); DeleteDC(memDC); EndPaint(hWnd, &ps); } break; case WM_DESTROY: // 退出前释放定时器和音频设备,避免 MCI 或多媒体定时器残留。 StopAppTimers(hWnd); StopBackgroundMusic(); timeEndPeriod(1); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, 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); switch (message) { case WM_INITDIALOG: SetWindowText(hDlg, _T("\u5173\u4e8e\u4fc4\u7f57\u65af\u65b9\u5757")); return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }