控制台俄罗斯方块,核心是数据不是图形:棋盘、方块矩阵、旋转、消行。每按一次键走一步,纯标准 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 行双倍分。