控制台俄罗斯方块,核心是数据不是图形:棋盘、方块矩阵、旋转、消行。每按一次键走一步,纯标准 C,Windows 和 Linux 都能编译运行。
1. 棋盘与七种方块
10×20 棋盘用二维数组,1 表示已固定。七种方块用 4×4 矩阵表示,当前方块记录类型、行、列:
#define COLS 10
#define ROWS 20
int board[ROWS][COLS];
int shapes[7][4][4] = {
{{1,1,1,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}}, // I
{{1,1},{1,1}}, // O
{{0,1,0},{1,1,1},{0,0,0},{0,0,0}}, // T
{{0,1,1},{1,1,0},{0,0,0},{0,0,0}}, // S
{{1,1,0},{0,1,1},{0,0,0},{0,0,0}}, // Z
{{1,0,0},{1,1,1},{0,0,0},{0,0,0}}, // J
{{0,0,1},{1,1,1},{0,0,0},{0,0,0}}, // L
};
int cur[4][4]; // 当前方块矩阵
int cur_r, cur_c;
2. 旋转与碰撞检测
顺时针旋转 = 转置后逐行反转。移动/旋转前先"试放",越界或撞到已固定方块就放弃:
void rotate_clock(int mat[4][4]) {
int tmp[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
tmp[j][3 - i] = mat[i][j];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
mat[i][j] = tmp[i][j];
}
int can_place(int mat[4][4], int r, int c) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (mat[i][j]) {
int rr = r + i, cc = c + j;
if (rr < 0 || rr >= ROWS || cc < 0 || cc >= COLS || board[rr][cc])
return 0;
}
return 1;
}
3. 固定与消行
放不下时把方块写进棋盘,然后从下往上找满行删除并补空行。计分:一次消 1/2/3/4 行分别是 100/300/500/800:
void lock(void) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (cur[i][j]) board[cur_r + i][cur_c + j] = 1;
}
int clear_lines(void) {
int cleared = 0;
for (int r = ROWS - 1; r >= 0; ) {
int full = 1;
for (int c = 0; c < COLS; c++)
if (!board[r][c]) { full = 0; break; }
if (full) {
for (int rr = r; rr > 0; rr--)
for (int c = 0; c < COLS; c++)
board[rr][c] = board[rr - 1][c];
for (int c = 0; c < COLS; c++) board[0][c] = 0;
cleared++;
} else r--;
}
return cleared;
}
4. 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define COLS 10
#define ROWS 20
int board[ROWS][COLS];
int shapes[7][4][4] = {
{{1,1,1,1},{0,0,0,0},{0,0,0,0},{0,0,0,0}},
{{1,1},{1,1}},
{{0,1,0},{1,1,1},{0,0,0},{0,0,0}},
{{0,1,1},{1,1,0},{0,0,0},{0,0,0}},
{{1,1,0},{0,1,1},{0,0,0},{0,0,0}},
{{1,0,0},{1,1,1},{0,0,0},{0,0,0}},
{{0,0,1},{1,1,1},{0,0,0},{0,0,0}},
};
int cur[4][4];
int cur_r, cur_c;
int score = 0;
void rotate_clock(int mat[4][4]) {
int tmp[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
tmp[j][3 - i] = mat[i][j];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
mat[i][j] = tmp[i][j];
}
int can_place(int mat[4][4], int r, int c) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (mat[i][j]) {
int rr = r + i, cc = c + j;
if (rr < 0 || rr >= ROWS || cc < 0 || cc >= COLS || board[rr][cc]) return 0;
}
return 1;
}
void lock(void) {
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if (cur[i][j]) board[cur_r + i][cur_c + j] = 1;
}
int clear_lines(void) {
int cleared = 0;
for (int r = ROWS - 1; r >= 0; ) {
int full = 1;
for (int c = 0; c < COLS; c++)
if (!board[r][c]) { full = 0; break; }
if (full) {
for (int rr = r; rr > 0; rr--)
for (int c = 0; c < COLS; c++)
board[rr][c] = board[rr - 1][c];
for (int c = 0; c < COLS; c++) board[0][c] = 0;
cleared++;
} else r--;
}
return cleared;
}
void spawn(void) {
int type = rand() % 7;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
cur[i][j] = shapes[type][i][j];
cur_r = 0; cur_c = 3;
}
void draw(void) {
system("clear"); // Windows 改成 system("cls")
for (int r = 0; r < ROWS; r++) {
printf("|");
for (int c = 0; c < COLS; c++) {
int shown = board[r][c];
for (int i = 0; i < 4 && !shown; i++)
for (int j = 0; j < 4 && !shown; j++)
if (cur[i][j] && r == cur_r + i && c == cur_c + j) shown = 1;
printf("%s", shown ? "[]" : " ");
}
printf("|\n");
}
printf("+--------------------+\n分数: %d a左 d右 s下 w旋转 空格落底 q退出\n", score);
}
int main(void) {
srand((unsigned)time(NULL));
spawn();
char key;
while (1) {
draw();
scanf(" %c", &key);
if (key == 'q') break;
if (key == 'a' && can_place(cur, cur_r, cur_c - 1)) cur_c--;
if (key == 'd' && can_place(cur, cur_r, cur_c + 1)) cur_c++;
if (key == 's' && can_place(cur, cur_r + 1, cur_c)) cur_r++;
if (key == 'w') {
int tmp[4][4];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++) tmp[i][j] = cur[i][j];
rotate_clock(tmp);
if (can_place(tmp, cur_r, cur_c))
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++) cur[i][j] = tmp[i][j];
}
if (key == ' ') {
while (can_place(cur, cur_r + 1, cur_c)) cur_r++;
}
if (!can_place(cur, cur_r + 1, cur_c)) {
lock();
int n = clear_lines();
score += (n == 1 ? 100 : n == 2 ? 300 : n == 3 ? 500 : n == 4 ? 800 : 0);
spawn();
if (!can_place(cur, cur_r, cur_c)) { printf("游戏结束,分数: %d\n", score); break; }
}
}
return 0;
}
5. 运行与常见问题
保存为 tetris.c,gcc tetris.c -o tetris 后运行(Windows 把 system("clear") 改成 system("cls"))。常见问题:①O 方块旋转越界——O 只有 2×2,旋转后检查 can_place 失败就保持原状;②消行后上面没掉下来——循环方向必须从下往上;③没反应——检查 can_place 边界条件。
💡 改进方向:①自动下落(非阻塞输入);②最高分存档;③下一块预览;④幽灵块;⑤一次消 4 行双倍分。