diff --git a/image.png b/image.png deleted file mode 100644 index 545a4b1..0000000 Binary files a/image.png and /dev/null differ diff --git a/src/include/Tetris.h b/src/include/Tetris.h index c8b0c72..6345397 100644 --- a/src/include/Tetris.h +++ b/src/include/Tetris.h @@ -49,7 +49,6 @@ void DropDown(); void Fixing(); void DeleteOneLine(int number); void DeleteLines(); -bool GameOver(); void ComputeTarget(); void Restart(); diff --git a/src/source/Tetris.cpp b/src/source/Tetris.cpp index 63bf6aa..e69c96d 100644 --- a/src/source/Tetris.cpp +++ b/src/source/Tetris.cpp @@ -188,8 +188,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else { Fixing(); - DeleteLines(); - gameOverFlag = GameOver(); + if (!gameOverFlag) + { + DeleteLines(); + } } if (!gameOverFlag) @@ -255,8 +257,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) else { Fixing(); - DeleteLines(); - gameOverFlag = GameOver(); + if (!gameOverFlag) + { + DeleteLines(); + } } break; case VK_UP: @@ -266,8 +270,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) case VK_SPACE: DropDown(); Fixing(); - DeleteLines(); - gameOverFlag = GameOver(); + if (!gameOverFlag) + { + DeleteLines(); + } break; default: break; diff --git a/src/source/TetrisLogic.cpp b/src/source/TetrisLogic.cpp index 51e0624..8be0bbc 100644 --- a/src/source/TetrisLogic.cpp +++ b/src/source/TetrisLogic.cpp @@ -69,6 +69,77 @@ COLORREF BrickColor[7] = RGB(197, 170, 255) }; +/** + * @brief 计算指定方块在指定旋转状态下的最小包围盒边界。 + * + * 该函数会遍历 4x4 形状矩阵,找出所有非空单元的上下左右边界, + * 供后续统一计算生成位置和对齐方式时使用。 + * + * @param brickType 方块类型编号。 + * @param brickState 方块旋转状态编号。 + * @param minRow 返回最上方非空行号。 + * @param maxRow 返回最下方非空行号。 + * @param minCol 返回最左侧非空列号。 + * @param maxCol 返回最右侧非空列号。 + */ +static void GetBrickBounds(int brickType, int brickState, int& minRow, int& maxRow, int& minCol, int& maxCol) +{ + minRow = 4; + maxRow = -1; + minCol = 4; + maxCol = -1; + + for (int i = 0; i < 4; i++) + { + for (int j = 0; j < 4; j++) + { + if (bricks[brickType][brickState][i][j] != 0) + { + if (i < minRow) + { + minRow = i; + } + if (i > 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); + } } } }