diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index c539f58..41bc0b8 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -3,6 +3,8 @@ #include "Tetris.h" #define MAX_LOADSTRING 100 +#define GAME_TIMER_ID 1 +#define GAME_TIMER_INTERVAL 500 HINSTANCE hInst; TCHAR szTitle[MAX_LOADSTRING]; @@ -31,6 +33,7 @@ int APIENTRY _tWinMain(HINSTANCE hInstance, LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadString(hInstance, IDC_TETRIS, szWindowClass, MAX_LOADSTRING); + lstrcpy(szTitle, _T("俄罗斯方块")); MyRegisterClass(hInstance); if (!InitInstance(hInstance, nCmdShow)) @@ -126,8 +129,8 @@ BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) /** * @brief 主窗口消息处理函数,负责响应菜单、绘制和退出等系统消息。 * - * 当前阶段主要处理“关于”和“退出”菜单命令、窗口重绘请求以及销毁消息, - * 其余未处理的消息统一交给系统默认窗口过程继续处理。 + * 该函数除菜单和绘制外,还负责初始化游戏、处理定时下落、 + * 响应键盘操作以及管理暂停、重开和游戏结束状态。 * * @param hWnd 当前窗口句柄。 * @param message 当前接收到的消息类型。 @@ -139,6 +142,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { + case WM_CREATE: + srand((unsigned int)time(nullptr)); + Restart(); + SetTimer(hWnd, GAME_TIMER_ID, GAME_TIMER_INTERVAL, nullptr); + InvalidateRect(hWnd, nullptr, TRUE); + break; case WM_COMMAND: { int wmId = LOWORD(wParam); @@ -156,6 +165,101 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } } break; + case WM_TIMER: + if (wParam == GAME_TIMER_ID && !suspendFlag && !gameOverFlag) + { + if (CanMoveDown()) + { + MoveDown(); + } + else + { + Fixing(); + DeleteLines(); + gameOverFlag = GameOver(); + } + + if (!gameOverFlag) + { + ComputeTarget(); + } + + InvalidateRect(hWnd, nullptr, TRUE); + } + break; + case WM_KEYDOWN: + if (wParam == 'R') + { + Restart(); + InvalidateRect(hWnd, nullptr, TRUE); + break; + } + + if (wParam == 'P') + { + suspendFlag = !suspendFlag; + InvalidateRect(hWnd, nullptr, TRUE); + break; + } + + if (wParam == 'G') + { + targetFlag = !targetFlag; + InvalidateRect(hWnd, nullptr, TRUE); + break; + } + + if (gameOverFlag || suspendFlag) + { + break; + } + + switch (wParam) + { + case VK_LEFT: + if (CanMoveLeft()) + { + MoveLeft(); + } + break; + case VK_RIGHT: + if (CanMoveRight()) + { + MoveRight(); + } + break; + case VK_DOWN: + if (CanMoveDown()) + { + MoveDown(); + } + else + { + Fixing(); + DeleteLines(); + gameOverFlag = GameOver(); + } + break; + case VK_UP: + Rotate(); + break; + case VK_SPACE: + DropDown(); + Fixing(); + DeleteLines(); + gameOverFlag = GameOver(); + break; + default: + break; + } + + if (!gameOverFlag) + { + ComputeTarget(); + } + + InvalidateRect(hWnd, nullptr, TRUE); + break; case WM_PAINT: { PAINTSTRUCT ps; @@ -165,6 +269,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } break; case WM_DESTROY: + KillTimer(hWnd, GAME_TIMER_ID); PostQuitMessage(0); break; default: @@ -193,6 +298,7 @@ INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) switch (message) { case WM_INITDIALOG: + SetWindowText(hDlg, _T("关于俄罗斯方块")); return (INT_PTR)TRUE; case WM_COMMAND: diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 1783e75..e7a106d 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -228,13 +228,40 @@ void MoveRight() /** * @brief 旋转当前活动方块到下一种朝向。 * - * 游戏中的每种方块都预置了 4 种旋转状态,该函数通过切换状态编号实现旋转。 - * 当状态增加到 4 时重新回到 0,形成循环旋转效果。 + * 游戏中的每种方块都预置了 4 种旋转状态,该函数会先尝试切换到下一状态, + * 然后检查旋转后的方块是否越界或与固定方块重叠。 + * 如果旋转后的状态非法,则恢复到旋转前的状态。 */ void Rotate() { - // 切换到下一种旋转状态 + int oldState = state; state = (state + 1) % 4; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[type][state][i][j] != 0) + { + int checkY = point.y + i; + int checkX = point.x + j; + + // 旋转后若越界,则恢复原状态 + if (checkX < 0 || checkX >= nGameWidth || checkY < 0 || checkY >= nGameHeight) + { + state = oldState; + return; + } + + // 旋转后若与固定方块重叠,则恢复原状态 + if (workRegion[checkY][checkX] != 0) + { + state = oldState; + return; + } + } + } + } } /** @@ -258,7 +285,7 @@ void DropDown() * 遍历当前方块 4x4 形状矩阵,把其中所有非空单元写入工作区数组, * 表示该方块已经落地并转为固定状态。 * 随后将“下一方块”切换为新的当前方块,重置旋转状态, - * 并把新方块生成到工作区上方的初始位置。 + * 并把新方块生成到工作区上方的初始位置,同时刷新预测落点。 */ void Fixing() { @@ -286,6 +313,8 @@ void Fixing() state = 0; point.x = 3; point.y = 0; + target = point; + ComputeTarget(); } /** diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 69e458d..a8763df 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -160,13 +160,13 @@ void TDrawScreen(HDC hdc, HWND hWnd) gameRect.bottom }; - DrawText(hdc, _T("Tetris"), -1, &infoRect, DT_TOP | DT_LEFT | DT_SINGLELINE); + DrawText(hdc, _T("俄罗斯方块"), -1, &infoRect, DT_TOP | DT_LEFT | DT_SINGLELINE); TCHAR scoreText[64]; - _stprintf_s(scoreText, _T("Score: %d"), tScore); + _stprintf_s(scoreText, _T("当前得分:%d"), tScore); TextOut(hdc, infoRect.left, infoRect.top + GRID * 2, scoreText, lstrlen(scoreText)); - TextOut(hdc, infoRect.left, infoRect.top + GRID * 4, _T("Next:"), 5); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 4, _T("下一个方块:"), lstrlen(_T("下一个方块:"))); RECT nextRect = { @@ -210,19 +210,31 @@ void TDrawScreen(HDC hdc, HWND hWnd) } } - TextOut(hdc, infoRect.left, infoRect.top + GRID * 11, _T("Controls:"), 9); - TextOut(hdc, infoRect.left, infoRect.top + GRID * 12, _T("Arrow Keys"), 10); - TextOut(hdc, infoRect.left, infoRect.top + GRID * 13, _T("Space: Drop"), 11); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 11, _T("操作说明:"), lstrlen(_T("操作说明:"))); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 12, _T("方向键:移动 / 旋转"), lstrlen(_T("方向键:移动 / 旋转"))); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 13, _T("空格:快速下落"), lstrlen(_T("空格:快速下落"))); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 14, _T("P:暂停 R:重开"), lstrlen(_T("P:暂停 R:重开"))); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 15, _T("G:显示/隐藏落点"), lstrlen(_T("G:显示/隐藏落点"))); if (suspendFlag) { - TextOut(hdc, infoRect.left, infoRect.top + GRID * 15, _T("Paused"), 6); + SetTextColor(hdc, RGB(220, 120, 0)); + TextOut(hdc, infoRect.left, infoRect.top + GRID * 17, _T("游戏已暂停"), lstrlen(_T("游戏已暂停"))); + SetTextColor(hdc, RGB(40, 40, 40)); } if (gameOverFlag) { RECT overRect = gameRect; SetTextColor(hdc, RGB(255, 80, 80)); - DrawText(hdc, _T("GAME OVER"), -1, &overRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + DrawText(hdc, _T("游戏结束"), -1, &overRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + RECT tipRect = + { + gameRect.left, + gameRect.top + GRID * 11, + gameRect.right, + gameRect.top + GRID * 13 + }; + DrawText(hdc, _T("按 R 键重新开始"), -1, &tipRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); } }