移除旧的GameOver判定并统一顶部溢出失败逻辑

This commit is contained in:
2026-04-24 11:45:27 +08:00
parent e8bbf609fd
commit 208b82b9f9
5 changed files with 135 additions and 77 deletions
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

-1
View File
@@ -49,7 +49,6 @@ void DropDown();
void Fixing();
void DeleteOneLine(int number);
void DeleteLines();
bool GameOver();
void ComputeTarget();
void Restart();
+9 -3
View File
@@ -188,8 +188,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
else
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
gameOverFlag = GameOver();
}
}
if (!gameOverFlag)
@@ -255,8 +257,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
else
{
Fixing();
if (!gameOverFlag)
{
DeleteLines();
gameOverFlag = GameOver();
}
}
break;
case VK_UP:
@@ -266,8 +270,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
case VK_SPACE:
DropDown();
Fixing();
if (!gameOverFlag)
{
DeleteLines();
gameOverFlag = GameOver();
}
break;
default:
break;
+96 -46
View File
@@ -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();
+4 -1
View File
@@ -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,6 +252,8 @@ void TDrawScreen(HDC hdc, HWND hWnd)
}
// 绘制当前活动方块
if (!gameOverFlag)
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
@@ -284,6 +286,7 @@ void TDrawScreen(HDC hdc, HWND hWnd)
}
}
}
}
// 绘制右侧信息面板
HFONT oldFont = (HFONT)SelectObject(hdc, titleFont);