2048 是近几年最成功的休闲小游戏:规则一句话讲完,实现却大有讲究。它的核心是"滑动 + 合并"算法:每次移动所有方块整体滑动,相同数字相撞就合并翻倍,直到合成 2048。

玩法介绍

棋盘 4×4,开局有两个方块(2 或 4)。按 W/A/S/D 整体移动,相同数字碰撞合并成它们的和,每步后随机生成一个新方块。合成 2048 即胜利;棋盘被填满且无相邻相同数字时,游戏结束。

第 1 步:棋盘与随机生成方块

全局数组 board[4][4] 表示棋盘,0 代表空格。spawn() 先收集所有空格坐标,再随机挑一个填入 2(90%)或 4(10%)。

int board[4][4];   /* 0 表示空格 */
int score = 0;

if (board[i][j] == 0) {        /* 收集空格 */
    empty[n][0] = i; empty[n][1] = j; n++;
}
i = rand() % n;                /* 随机挑一个 */
board[empty[i][0]][empty[i][1]] = (rand() % 10 < 9) ? 2 : 4;

第 2 步:打印棋盘

宽度修饰符 %4d 让数字右对齐占 4 格,横线分隔行,终端里就是一块整齐的棋盘。

printf("\n+----+----+----+----+\n");
for (i = 0; i < 4; i++) {
    for (j = 0; j < 4; j++)
        printf("|%4d", board[i][j]);
    printf("|\n+----+----+----+----+\n");
}

第 3 步:单行滑动合并(核心算法)

把"向左滑动一行"拆成两步:先压缩——非零数字靠左、空格挤到右边;再合并——相邻相同的两个数字相加,后面的数字整体左移。返回是否变化,决定是否生成新方块。写算法前先备好测试用例:[2,2,2,2] → [4,4,0,0],[2,2,4,4] → [4,8,0,0],[4,4,4,0] → [8,4,0,0]

j = 0;                                 /* 压缩: 数字靠左 */
for (i = 0; i < 4; i++)
    if (row[i] != 0) row[j++] = row[i];
for (; j < 4; j++) row[j] = 0;

for (i = 0; i < 3; i++) {              /* 合并相邻相同 */
    if (row[i] != 0 && row[i] == row[i + 1]) {
        row[i] *= 2;
        score += row[i];
        for (k = i + 1; k < 3; k++) row[k] = row[k + 1];
        row[3] = 0;
    }
}

第 4 步:转置与镜像实现四个方向

已经会"向左",其他方向怎么办?转置(行列互换)把"向上"变成"向左",镜像(每行左右反转)把"向右"变成"向左"。先变换、再左移、最后变回去,四个方向共用同一段核心代码。

for (i = 0; i < 4; i++)               /* 转置: 行列互换 */
    for (j = 0; j < 4; j++)
        tmp[i][j] = board[j][i];

/* 上移 = 转置 → 左移 → 转置; 右移 = 镜像 → 左移 → 镜像 */

第 5 步:胜负判定与主循环

胜利条件:棋盘出现 2048;失败条件:无空格且横向、纵向都没有相邻相同数字。主循环是"读输入 → 移动 → 生成新块 → 检查胜负",棋盘真的变化时才 spawn()

if (board[i][j] == 0) return 0;                        /* 还有空格 */
if (j < 3 && board[i][j] == board[i][j + 1]) return 0;  /* 横向可合并 */
if (i < 3 && board[i][j] == board[i + 1][j]) return 0;  /* 纵向可合并 */

第 6 步:完整代码与编译运行

把各步函数拼在一起,就是可运行的控制台 2048:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int board[4][4];   /* 0 表示空格 */
int score = 0;

/* 随机空格生成 2(90%)或 4(10%) */
void spawn(void) {
    int empty[16][2], n = 0, i, j;
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
            if (board[i][j] == 0) { empty[n][0] = i; empty[n][1] = j; n++; }
    if (n == 0) return;
    i = rand() % n;
    board[empty[i][0]][empty[i][1]] = (rand() % 10 < 9) ? 2 : 4;
}

/* 单行左滑: 压缩 + 合并, 返回是否变化 */
int slide_left_row(int row[4]) {
    int orig[4], i, j, k;
    for (i = 0; i < 4; i++) orig[i] = row[i];
    j = 0;
    for (i = 0; i < 4; i++)
        if (row[i] != 0) row[j++] = row[i];
    for (; j < 4; j++) row[j] = 0;
    for (i = 0; i < 3; i++) {
        if (row[i] != 0 && row[i] == row[i + 1]) {
            row[i] *= 2;
            score += row[i];
            for (k = i + 1; k < 3; k++) row[k] = row[k + 1];
            row[3] = 0;
        }
    }
    for (i = 0; i < 4; i++)
        if (row[i] != orig[i]) return 1;
    return 0;
}

void transpose(void) {
    int tmp[4][4], i, j;
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++) tmp[i][j] = board[j][i];
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++) board[i][j] = tmp[i][j];
}

void reverse_rows(void) {
    int tmp[4][4], i, j;
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++) tmp[i][j] = board[i][3 - j];
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++) board[i][j] = tmp[i][j];
}

/* dir: 0左 1右 2上 3下 */
int move(int dir) {
    int changed = 0, i;
    if (dir == 1) reverse_rows();
    if (dir == 2) transpose();
    if (dir == 3) { transpose(); reverse_rows(); }
    for (i = 0; i < 4; i++)
        changed |= slide_left_row(board[i]);
    if (dir == 1) reverse_rows();
    if (dir == 2) transpose();
    if (dir == 3) { reverse_rows(); transpose(); }
    return changed;
}

void print_board(void) {
    int i, j;
    printf("\n+----+----+----+----+\n");
    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++)
            printf("|%4d", board[i][j]);
        printf("|\n+----+----+----+----+\n");
    }
    printf("得分: %d\n", score);
}

int has_2048(void) {
    int i, j;
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++)
            if (board[i][j] >= 2048) return 1;
    return 0;
}

int game_over(void) {
    int i, j;
    for (i = 0; i < 4; i++)
        for (j = 0; j < 4; j++) {
            if (board[i][j] == 0) return 0;
            if (j < 3 && board[i][j] == board[i][j + 1]) return 0;
            if (i < 3 && board[i][j] == board[i + 1][j]) return 0;
        }
    return 1;
}

int main(void) {
    char cmd;
    int dir;
    srand((unsigned)time(NULL));
    spawn();
    spawn();
    while (1) {
        print_board();
        printf("W/A/S/D 移动, Q 退出: ");
        scanf(" %c", &cmd);
        switch (cmd) {
        case 'a': case 'A': dir = 0; break;
        case 'd': case 'D': dir = 1; break;
        case 'w': case 'W': dir = 2; break;
        case 's': case 'S': dir = 3; break;
        case 'q': case 'Q': return 0;
        default: continue;
        }
        if (move(dir)) spawn();
        if (has_2048()) { print_board(); printf("太棒了! 你合成了 2048!\n"); break; }
        if (game_over()) { print_board(); printf("游戏结束: 没有可移动的方块了。\n"); break; }
    }
    return 0;
}

编译运行:

gcc -o game2048 game2048.c
./game2048
💡 改进方向:把最高分写进文件做成存档;增加"撤销一步"(保存上一步棋盘);用 ANSI 转义序列给数字上色;写一个简单的评估函数让电脑自动玩;再把同样的算法移植成网页版或图形界面版。