理解矩阵旋转的原理,学会如何实现方块的 90 度旋转。
核心公式:
新位置 [j][3-i] = 原位置 [i][j]
图解:
原矩阵 (2x2 示例): 旋转后:
[0][0] [0][1] [1][0] [0][0]
[1][0] [1][1] → [1][1] [0][1]
规律:
- [0][0] → [0][1] (第 0 行第 0 列 → 第 0 行第 1 列)
- [0][1] → [1][1] (第 0 行第 1 列 → 第 1 行第 1 列)
- [1][0] → [0][0] (第 1 行第 0 列 → 第 0 行第 0 列)
- [1][1] → [1][0] (第 1 行第 1 列 → 第 1 行第 0 列)
T 方块旋转 90 度:
旋转前 (0 度): 旋转后 (90 度):
0 0 0 0 0 0 1 0
1 1 1 0 → 0 1 1 0
0 1 0 0 0 0 1 0
0 0 0 0 0 0 0 0
坐标变换:
[1][0]=1 → [0][2]=1
[1][1]=1 → [1][2]=1
[1][2]=1 → [2][2]=1
[2][1]=1 → [1][1]=1
// tetromino.c
void tetromino_rotate(Tetromino* t) {
int temp[4][4];
int old_shape[4][4];
// 1. 保存原形状
memcpy(old_shape, t->shape, sizeof(t->shape));
// 2. 顺时针旋转 90 度
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp[j][3-i] = t->shape[i][j];
}
}
// 3. 应用旋转
memcpy(t->shape, temp, sizeof(t->shape));
t->rotation = (t->rotation + 1) % 4;
}
步骤 1:为什么需要 temp 数组?
// ❌ 错误做法:直接修改原数组
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
t->shape[j][3-i] = t->shape[i][j];
// 问题:后面的迭代会用到已修改的值!
}
}
// ✅ 正确做法:用临时数组
int temp[4][4];
// 先写入 temp,最后再复制回 t->shape
步骤 2:旋转公式解析
temp[j][3-i] = t->shape[i][j];
// 示例:i=1, j=0
// t->shape[1][0] = 1 (T 方块的左边)
// temp[0][3-1] = temp[0][2] = 1
// 结果:[1][0] → [0][2]
步骤 3:更新旋转状态
t->rotation = (t->rotation + 1) % 4;
// 旋转状态:0 → 1 → 2 → 3 → 0 → ...
// % 4 确保在 0-3 之间循环
// input.c
case KEY_UP:
case 'k':
case 'w':
{
Tetromino temp = game->current;
tetromino_rotate(&temp);
// 检查旋转后是否碰撞
if (!board_check_collision(&game->board, &temp)) {
game->current = temp;
}
}
break;
场景:方块在墙边
旋转前: 直接旋转:
┌──────┐ ┌──────┐
│█ │ │█ │ ← 旋转后超出边界!
│███ │ → │███ │
└──────┘ └──────┘
正确做法:
1. 创建临时副本
2. 旋转副本
3. 检查副本是否碰撞
4. 如果不碰撞,应用旋转
5. 如果碰撞,保持原样
// 虽然代码中用 0-3 表示,但理解这四个状态很重要
typedef enum {
ROTATION_0, // 0 度(初始)
ROTATION_90, // 顺时针 90 度
ROTATION_180, // 180 度
ROTATION_270 // 顺时针 270 度(或逆时针 90 度)
} RotationState;
T 方块连续旋转:
0 度: 90 度:
0 0 0 0 0 0 1 0
1 1 1 0 → 0 1 1 0
0 1 0 0 0 0 1 0
0 0 0 0 0 0 0 0
180 度: 270 度:
0 1 0 0 0 0 0 0
1 1 1 0 → 0 1 1 0
0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0
再转一次回到 0 度...
// O 方块的形状
int O_shape[4][4] = {
{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
// 旋转 90 度后:
int O_rotated[4][4] = {
{0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}
};
// 结果:完全一样!
优化: 可以为 O 方块跳过旋转检查
void tetromino_rotate(Tetromino* t) {
// O 方块不需要旋转
if (t->type == TETRO_O) {
return;
}
// ... 正常旋转逻辑
}
逆时针 90 度公式:
新位置 [3-j][i] = 原位置 [i][j]
代码:
void tetromino_rotate_counter_clockwise(Tetromino* t) {
int temp[4][4];
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
temp[3-j][i] = t->shape[i][j];
}
}
memcpy(t->shape, temp, sizeof(t->shape));
t->rotation = (t->rotation + 3) % 4; // 逆时针 = 顺时针 3 次
}
// 旋转前后打印形状
void print_shape(int shape[4][4]) {
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", shape[i][j]);
}
printf("\n");
}
printf("\n");
}
// 使用
printf("旋转前:\n");
print_shape(t->shape);
tetromino_rotate(t);
printf("旋转后:\n");
print_shape(t->shape);
手动计算 T 方块从 0 度旋转到 90 度后,每个 1 的新位置
实现逆时针旋转函数,并测试
为 O 方块添加旋转优化(跳过旋转)
下一课:消行逻辑