用C++实现俄罗斯方块时,方块旋转和碰撞检测是保证游戏正常运行的核心逻辑。方块旋转需要定义不同形状的所有旋转状态,碰撞检测则需要判断方块移动或旋转后的位置是否超出边界或者与已落下的方块重叠。

方块的数据结构设计
首先需要定义方块的基础数据结构,我们可以用二维数组表示方块的每个旋转状态,用坐标记录当前方块的左上角位置。以下是基础的结构定义:
// 定义方块的形状,每个元素是一个旋转状态,每个旋转状态是4x4的二维数组
// 1表示有方块,0表示空
int blockShapes[7][4][4][4] = {
// I型方块
{
{{0,0,0,0},{1,1,1,1},{0,0,0,0},{0,0,0,0}},
{{0,0,1,0},{0,0,1,0},{0,0,1,0},{0,0,1,0}},
{{0,0,0,0},{0,0,0,0},{1,1,1,1},{0,0,0,0}},
{{0,1,0,0},{0,1,0,0},{0,1,0,0},{0,1,0,0}}
},
// O型方块
{
{{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
{{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
{{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}},
{{0,0,0,0},{0,1,1,0},{0,1,1,0},{0,0,0,0}}
}
// 其他形状省略,结构类似
};
// 当前方块的状态
struct CurrentBlock {
int type; // 方块类型 0-6
int rotate; // 旋转状态 0-3
int x; // 方块左上角x坐标(列)
int y; // 方块左上角y坐标(行)
} currentBlock;
方块旋转的实现逻辑
方块旋转的核心是切换当前方块的旋转状态,旋转后需要先进行碰撞检测,如果检测不通过则回退旋转状态。旋转状态的切换逻辑很简单,顺时针旋转时状态加1,超过3则回到0,逆时针旋转则相反。
旋转状态切换代码
// 顺时针旋转方块
void rotateBlockClockwise() {
int oldRotate = currentBlock.rotate;
// 切换旋转状态
currentBlock.rotate = (currentBlock.rotate + 1) % 4;
// 检测旋转后是否碰撞
if (checkCollision(currentBlock.x, currentBlock.y, currentBlock.type, currentBlock.rotate)) {
// 碰撞则回退状态
currentBlock.rotate = oldRotate;
}
}
// 逆时针旋转方块
void rotateBlockCounterClockwise() {
int oldRotate = currentBlock.rotate;
currentBlock.rotate = (currentBlock.rotate - 1 + 4) % 4;
if (checkCollision(currentBlock.x, currentBlock.y, currentBlock.type, currentBlock.rotate)) {
currentBlock.rotate = oldRotate;
}
}
碰撞检测的实现方法
碰撞检测需要验证三个条件:方块位置没有超出游戏区域的左右边界、没有超出底部边界、没有和已经固定的方块重叠。游戏区域通常用一个二维数组表示,0表示空,1表示有固定方块。
碰撞检测函数实现
// 游戏区域大小,行和列
const int GAME_ROWS = 20;
const int GAME_COLS = 10;
// 游戏区域数组,存储已经固定的方块
int gameArea[GAME_ROWS][GAME_COLS] = {0};
// 碰撞检测函数,参数分别是方块的x、y坐标,类型,旋转状态
bool checkCollision(int x, int y, int type, int rotate) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
// 如果当前位置方块有元素
if (blockShapes[type][rotate][i][j] == 1) {
int realX = x + j;
int realY = y + i;
// 检测是否超出左右边界
if (realX < 0 || realX >= GAME_COLS) {
return true;
}
// 检测是否超出底部边界
if (realY >= GAME_ROWS) {
return true;
}
// 检测是否和已固定的方块重叠,顶部超出不需要检测
if (realY >= 0 && gameArea[realY][realX] == 1) {
return true;
}
}
}
}
return false;
}
实际调用示例
在游戏主循环中,当玩家按下旋转键时调用旋转函数,移动方块时也需要先调用碰撞检测验证位置是否合法。以下是简单的调用示例:
// 处理玩家输入
void handleInput() {
// 假设按下上键顺时针旋转
if (keyUpPressed) {
rotateBlockClockwise();
}
// 按下左键向左移动
if (keyLeftPressed) {
if (!checkCollision(currentBlock.x - 1, currentBlock.y, currentBlock.type, currentBlock.rotate)) {
currentBlock.x--;
}
}
// 按下右键向右移动
if (keyRightPressed) {
if (!checkCollision(currentBlock.x + 1, currentBlock.y, currentBlock.type, currentBlock.rotate)) {
currentBlock.x++;
}
}
// 按下下键快速下落
if (keyDownPressed) {
if (!checkCollision(currentBlock.x, currentBlock.y + 1, currentBlock.type, currentBlock.rotate)) {
currentBlock.y++;
}
}
}
注意事项
- 方块形状的定义要准确,每个旋转状态的坐标要对应正确,避免出现旋转后形状异常的问题
- 碰撞检测时要考虑方块在游戏区域顶部的情况,顶部超出不需要判定为碰撞,否则新生成的方块无法显示
- 旋转和移动操作都要先检测再执行,避免方块卡进边界或者重叠
- 如果方块有特殊的旋转中心,需要调整形状数组的定义,保证旋转后的位置符合预期