From 208b82b9f9cec61ceb231c673da0ce802b6a5c65 Mon Sep 17 00:00:00 2001 From: Qi-huanye <2728290997@qq.com> Date: Fri, 24 Apr 2026 11:45:27 +0800 Subject: [PATCH] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E6=97=A7=E7=9A=84GameOver?= =?UTF-8?q?=E5=88=A4=E5=AE=9A=E5=B9=B6=E7=BB=9F=E4=B8=80=E9=A1=B6=E9=83=A8?= =?UTF-8?q?=E6=BA=A2=E5=87=BA=E5=A4=B1=E8=B4=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- image.png | Bin 8427 -> 0 bytes src/include/Tetris.h | 1 - src/source/Tetris.cpp | 18 +++-- src/source/TetrisLogic.cpp | 142 ++++++++++++++++++++++++------------ src/source/TetrisRender.cpp | 51 +++++++------ 5 files changed, 135 insertions(+), 77 deletions(-) delete mode 100644 image.png diff --git a/image.png b/image.png deleted file mode 100644 index 545a4b1afc6b7c46da5c05f6f1c9957b3ead714c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8427 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 maxRow) + { + maxRow = i; + } + if (j < minCol) + { + minCol = j; + } + if (j > maxCol) + { + maxCol = j; + } + } + } + } +} + +/** + * @brief 计算指定方块的统一生成位置。 + * + * 该函数会根据方块在初始旋转状态下的最小包围盒, + * 自动把方块水平居中到游戏区附近,并将顶部非空行对齐到可视区域顶部。 + * 这样不同形状的方块在生成时看起来会更加统一。 + * + * @param brickType 方块类型编号。 + * @return Point 计算得到的生成坐标。 + */ +static Point GetSpawnPoint(int brickType) +{ + int minRow, maxRow, minCol, maxCol; + GetBrickBounds(brickType, 0, minRow, maxRow, minCol, maxCol); + + int brickWidth = maxCol - minCol + 1; + int brickHeight = maxRow - minRow + 1; + Point spawnPoint; + spawnPoint.x = (nGameWidth - brickWidth) / 2 - minCol; + spawnPoint.y = -brickHeight; + + return spawnPoint; +} + /** * @brief 判断当前方块是否可以继续向下移动。 * @@ -98,7 +169,7 @@ bool CanMoveDown() } // 检查下方是否有其他固定方块 - if (workRegion[nextY][nextX] != 0) + if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } @@ -138,7 +209,7 @@ bool CanMoveLeft() } // 检查左侧是否有其他固定方块 - if (workRegion[nextY][nextX] != 0) + if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } @@ -178,7 +249,7 @@ bool CanMoveRight() } // 检查右侧是否有其他固定方块 - if (workRegion[nextY][nextX] != 0) + if (nextY >= 0 && workRegion[nextY][nextX] != 0) { return false; } @@ -247,14 +318,14 @@ void Rotate() int checkX = point.x + j; // 旋转后若越界,则恢复原状态 - if (checkX < 0 || checkX >= nGameWidth || checkY < 0 || checkY >= nGameHeight) + if (checkX < 0 || checkX >= nGameWidth || checkY >= nGameHeight) { state = oldState; return; } // 旋转后若与固定方块重叠,则恢复原状态 - if (workRegion[checkY][checkX] != 0) + if (checkY >= 0 && workRegion[checkY][checkX] != 0) { state = oldState; return; @@ -284,11 +355,15 @@ void DropDown() * * 遍历当前方块 4x4 形状矩阵,把其中所有非空单元写入工作区数组, * 表示该方块已经落地并转为固定状态。 - * 随后将“下一方块”切换为新的当前方块,重置旋转状态, + * 如果固定时仍有任意非空单元位于可视区域顶部之外,则判定游戏结束。 + * 此时当前方块在可视区域内的部分仍会保留在工作区中。 + * 若未超出顶部,再将“下一方块”切换为新的当前方块,重置旋转状态, * 并把新方块生成到工作区上方的初始位置,同时刷新预测落点。 */ void Fixing() { + bool overflowTop = false; + for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) @@ -298,7 +373,13 @@ void Fixing() int fixY = point.y + i; int fixX = point.x + j; - // 将当前方块的非空单元写入工作区 + // 只要当前方块任意非空单元仍超出顶部,就标记为结束 + if (fixY < 0) + { + overflowTop = true; + } + + // 将当前方块在可视区域内的部分写入工作区 if (fixY >= 0 && fixY < nGameHeight && fixX >= 0 && fixX < nGameWidth) { workRegion[fixY][fixX] = bricks[type][state][i][j]; @@ -307,12 +388,17 @@ void Fixing() } } + if (overflowTop) + { + gameOverFlag = true; + return; + } + // 生成下一个活动方块 type = nType; nType = rand() % 7; state = 0; - point.x = 3; - point.y = 0; + point = GetSpawnPoint(type); target = point; ComputeTarget(); } @@ -374,41 +460,6 @@ void DeleteLines() } } -/** - * @brief 判断游戏是否结束。 - * - * 当新的活动方块生成到初始位置后,如果它的任意一个非空单元 - * 与工作区中已经固定的方块发生重叠,则说明顶部已被占满, - * 当前局面无法继续生成新方块,游戏结束。 - * - * @return bool 若游戏结束返回 true,否则返回 false。 - */ -bool GameOver() -{ - 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 (checkY >= 0 && checkY < nGameHeight && checkX >= 0 && checkX < nGameWidth) - { - if (workRegion[checkY][checkX] != 0) - { - return true; - } - } - } - } - } - - return false; -} - /** * @brief 计算当前活动方块的预测落点位置。 * @@ -460,8 +511,7 @@ void Restart() type = rand() % 7; nType = rand() % 7; state = 0; - point.x = 3; - point.y = 0; + point = GetSpawnPoint(type); target = point; ComputeTarget(); diff --git a/src/source/TetrisRender.cpp b/src/source/TetrisRender.cpp index 07a35f0..23b97e0 100644 --- a/src/source/TetrisRender.cpp +++ b/src/source/TetrisRender.cpp @@ -217,7 +217,7 @@ void TDrawScreen(HDC hdc, HWND hWnd) } // 绘制预测落点 - if (targetFlag) + if (targetFlag && !gameOverFlag) { HPEN targetPen = CreatePen(PS_DOT, SS(2), RGB(255, 240, 245)); oldPen = (HPEN)SelectObject(hdc, targetPen); @@ -252,34 +252,37 @@ void TDrawScreen(HDC hdc, HWND hWnd) } // 绘制当前活动方块 - for (int i = 0; i < 4; i++) + if (!gameOverFlag) { - for (int j = 0; j < 4; j++) + for (int i = 0; i < 4; i++) { - if (bricks[type][state][i][j] != 0) + for (int j = 0; j < 4; j++) { - int drawY = point.y + i; - int drawX = point.x + j; - - if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) + if (bricks[type][state][i][j] != 0) { - RECT brickRect = - { - gameRect.left + drawX * grid + SS(2), - gameRect.top + drawY * grid + SS(2), - gameRect.left + (drawX + 1) * grid - SS(2), - gameRect.top + (drawY + 1) * grid - SS(2) - }; + int drawY = point.y + i; + int drawX = point.x + j; - HBRUSH brickBrush = CreateSolidBrush(BrickColor[type]); - HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 250, 252)); - oldPen = (HPEN)SelectObject(hdc, brickPen); - oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); - RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(12), SS(12)); - SelectObject(hdc, oldBrush); - SelectObject(hdc, oldPen); - DeleteObject(brickBrush); - DeleteObject(brickPen); + if (drawY >= 0 && drawY < nGameHeight && drawX >= 0 && drawX < nGameWidth) + { + RECT brickRect = + { + gameRect.left + drawX * grid + SS(2), + gameRect.top + drawY * grid + SS(2), + gameRect.left + (drawX + 1) * grid - SS(2), + gameRect.top + (drawY + 1) * grid - SS(2) + }; + + HBRUSH brickBrush = CreateSolidBrush(BrickColor[type]); + HPEN brickPen = CreatePen(PS_SOLID, 1, RGB(255, 250, 252)); + oldPen = (HPEN)SelectObject(hdc, brickPen); + oldBrush = (HBRUSH)SelectObject(hdc, brickBrush); + RoundRect(hdc, brickRect.left, brickRect.top, brickRect.right, brickRect.bottom, SS(12), SS(12)); + SelectObject(hdc, oldBrush); + SelectObject(hdc, oldPen); + DeleteObject(brickBrush); + DeleteObject(brickPen); + } } } }