前期准备:

使用软件:vscode

使用函数库:ncurses

安装ncurses绘图库

sudo apt-get install libncurses5-dev

设计思路

要实现 2048 游戏目前有两个关键点:

  1. 在满足条件情况下消除方块
  2. 允许在游戏主界面(16 宫格)中任意一格输出数据

其中第二点借助 ncurses 库可以较容易实现,但是第一点要稍微麻烦些。

第一点的实现思路是,我们创建一个与游戏地图相同维数的数组矩阵,通过数组矩阵来维护 2048 游戏中每个格子的数据与状态,从而玩家的移动操作都可以映射为对数组矩阵的操作。
在这里插入图片描述

绘制窗格

添加需要的头文件和全局变量

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

void init(void);//初始化
void draw_grid(void);//绘制窗格(游戏界面)
void draw_one(int x, int y);//绘制单个数字 传入数组的坐标
void play(void);//游戏运行
int cnt_one(int x, int y);
void cnt_value(int* new_x,int* new_y);
int game_over(void);//游戏结束

int board[4][4] = {0};//用来充当棋盘 4*4 16宫格 0表示空格子
int empty;//用来标记剩余空格子的数量(标记是否有相同的数字合并 有则+1)
int old_x, old_y;//用来标记避免在同一位置生成新数字

绘制5行横线,每行由21个-组成;绘制5列竖线,每列由7个|组成;

//绘制窗格
void draw_grid(void){
    int m, n, x, y;
    clear();//将整个屏幕清除
    //绘制横线 m行 n列
    for(m = 0; m < 9; m += 2){
        for(n = 0; n < 21; n++){
            move(m,n);//将光标移动至 m,n 的位置.
            addch('-');//在当前光标位置输入单个字符,并将光标右移一位。
            refresh();//刷新屏幕,使绘制即时生效
        }
    }
    //绘制竖线
    for (n = 0; n < 22; n += 5){
        for(m = 1; m < 8; m++){
            move(m,n);
            addch('|');
            refresh();
        }
    }
    //绘制数组中的全部数字
    for (x = 0; x < 4; x++){
        for(y = 0; y < 4; y++)
            draw_one(x, y);//绘制单个数字
    }
}


//绘制单个数字
void draw_one(int x, int y){
    int i, k, j, m=0;
    char c[5] = {0x00};
    i = board[x][y];
    while(i > 0){
        j = i % 10;//取出个位
        c[m++] = j + '0';//将数字转化为字符型
        i = i / 10;//去掉个位,剩下的继续循环 依次转化为字符型 c[5]倒序存入数字
    }
    m=0;
    k=(y + 1) * 5 - 1;
    while(c[m] != 0x00){
        move(2*x+1, k);//移动光标到格子中
        addch(c[m++]);//将字符取出画在屏幕上 并m++移到下一位字符
        k--;//使画出的数字变为正序
    }
}

//初始化
void init(void){
    int x, y;
    initscr();//开启 curses 模式
    cbreak();//开启后, 除了 DELETE 或 CTRL 等仍被视为特殊控制字元外一切输入的字元将立刻被一一读取
    noecho();//用来控制从键盘输入字元时不将字元显示在终端(窗口)上
    curs_set(0);//将把光标设置为不可见

    empty = 15;//初始化时剩余空格子的数量
    srand(time(0));//随机数种子
    x = rand() % 4;// 0 1 2 3
    y = rand() % 4;// 0 1 2 3
    board[x][y] = 2;//初始化随机在数组某处存入第一个数字2
    draw_grid();//绘制窗格
}

效果图如下:
在这里插入图片描述

核心函数

然后来看看游戏运行的核心 play 函数。通过 W, S, A, D 和方向键来分别控制上、下、左、右方向的移动,按 Q 键可以退出界面。(先分析出一个方向的逻辑,其余的类似)

void play(void){
    int x, y, i, new_x, new_y,temp;//x,y 表示 x行 y列 遍历数组使用;new_x,new_y表示新出现数字的坐标
    int old_empty, move;//move 标记是否有元素移动过 有则标记为1,有移动必定有空格出现

    while(1){
        move = 0;
        old_empty = empty;
        //获取用户的按键 进行上下左右操作的判断
        switch(getch()){
            case 'a':
            case 68://左移方向键 
                for(x = 0; x < 4; x++){
                    for(y = 0; y < 4;){
                        if(board[x][y] == 0){//持续判断数组中元素是否为0;即格子中是否为空
                            y++;
                            continue;
                        }
                        else{
                            for(i = y+1; i < 4; i++){
                                if(board[x][i] == 0)//数组中前一个元素不为0时;持续判断后面一个元素是否为0
                                    continue;
                                else{
                                    if(board[x][y] == board[x][i]){//如果一行左右两个都有元素是否相等(元素并不一定相邻)
                                        board[x][y] += board[x][i];//把左右两个元素相加,赋值给左边的元素(因为按下左键)
                                        board[x][i] = 0;//右边的元素清零
                                        empty++;//空格子数量+1
                                        break;
                                    }
                                    else
                                        break;//结束这一行的判断
                                }
                            }
                            y = i;//判断到这行有两个元素,把y定位到右边的一个;再次循环判断这一行后面是否有第三个元素 ,再次进行上面操作
                        }
                    }
                }//到此把四行中的左右相等的元素都进行相加 下面的循环将数据左对齐
                for(x = 0; x < 4; x++){
                    for(y = 0; y < 4; y++){
                        if(board[x][y] == 0)//判断到元素为0则继续循环
                            continue;
                        else
                            for(i = y; (i > 0) && (board[x][i-1] == 0); i--){//当执行这层循环时,说明左边有空位,要把左边的数据移过去
                                board[x][i-1] = board[x][i];
                                board[x][i] = 0;
                                move = 1;
                            }
                    }
                }
                break;//处理完左键的操作,跳出switch
            case 'd':
            case 67://右移方向键
            // case 77:
                for(x = 0; x < 4; x++){
                    for(y = 3; y >= 0; ){//右移从右往左遍历
                        if(board[x][y] == 0){
                            y--;
                            continue;
                        }
                        else{
                            for(i = y - 1; i >= 0; i--){//检测到有非零元素,继续向左检测元素
                                if(board[x][i] == 0)
                                    continue;
                                else if(board[x][y] == board[x][i]){
                                    board[x][y] += board[x][i];//把元素相加放在右边
                                    board[x][i] = 0;//左边的元素清零
                                    empty++;
                                    break;
                                }
                                else
                                    break;
                            }
                            y = i;
                        }
                    }
                }
                for(x = 0; x < 4; x++){
                    for(y = 3; y >= 0; y--){
                        if(board[x][y] == 0)
                            continue;
                         else
                            for(i = y; (i < 3) && (board[x][i+1] == 0); i++){//数组元素右对齐存放
                                board[x][i+1] = board[x][i];
                                board[x][i] = 0;
                                move = 1;
                            }
                    }
                }
                break;
            case 'w':
            case 65://上移方向键
                for(y = 0; y < 4; y++){//从第一列自上而下遍历数组
                    for(x = 0; x < 4; ){
                        if(board[x][y] == 0){
                            x++;
                            continue;
                        }
                        else{
                            for(i = x + 1; i < 4; i++){
                                if(board[i][y] == 0)
                                    continue;
                                else if(board[x][y] == board[i][y]){//上下两个元素相同则相加
                                    board[x][y] += board[i][y];
                                    board[i][y] = 0;
                                    empty++;
                                    break;
                                }
                                else
                                    break;
                            }
                            x = i;//将位置定位到下面的元素,遍历后面的元素
                        }
                    }
                }
                for(y = 0; y < 4; y++){
                    for(x = 0; x < 4; x++){
                        if(board[x][y] == 0)
                            continue;
                        else
                            for(i = x; (i > 0) && (board[i-1][y] == 0); i--){//当前位置有元素,并且上面一个元素为0,则位置上移
                                board[i-1][y] = board[i][y];
                                board[i][y] = 0;
                                move = 1;
                            }
                    }
                }
                break;
            case 's':
            case 66://下移方向键
                for(y = 0; y < 4; y++){//从第一列开始自下而上遍历数组
                    for(x = 3; x >= 0; ){
                        if(board[x][y] == 0){
                            x--;
                            continue;
                        }
                        else{
                            for(i = x - 1; i >= 0; i--){
                                if(board[i][y] == 0)
                                    continue;
                                else if(board[x][y] == board[i][y]){
                                    board[x][y] += board[i][y];
                                    board[i][y] = 0;
                                    empty++;
                                    break;
                                }
                                else
                                    break;
                            }
                            x = i;
                        }
                    }
                }
                for(y = 0; y < 4; y++){
                    for(x = 3; x >= 0; x--){
                        if(board[x][y] == 0)
                            continue;
                        else
                            for(i = x; (i < 3) && (board[i+1][y] == 0); i++){//如果当前元素不为零,且下面的元素为零
                                board[i+1][y] = board[i][y];
                                board[i][y] = 0;
                                move = 1;
                            }
                    }
                }
                break;
            case 'Q':
            case 'q':
                game_over();
                break;
            default:
                continue;
                break;
        }
        if(empty <= 0)//没有空格子,游戏结束
            game_over();
        if((empty != old_empty) || (move == 1)){//如果还有空格子
            do{
                new_x = rand() % 4;
                new_y = rand() % 4;
            }while(board[new_x][new_y] != 0);//找到数组中一个地方元素为0(格子为空)

            cnt_value(&new_x, &new_y);//避免在同一位置重复出现新数字

            do {
                temp = rand() % 4;
            }while(temp == 0 || temp == 2);//让temp为1或3
            board[new_x][new_y] = temp + 1;//随机出现的2或4放入数组的空格子
            empty--;//空格子数量-1
        }
        draw_grid();//数组更新后重新绘制窗格
    }
}

剩余部分

//统计(x, y)对应的格子周围一圈的空格子的个数
int cnt_one(int x, int y){
    int value = 0;
    if(x - 1 >= 0)
        board[x - 1][y] ? 0 : value++;//判断上边的格子是否为0 判断了2 3 4行
    if(x + 1 < 4)
        board[x + 1][y] ? 0 : value++;//判断下边的格子是否为0 判断了1 2 3行
    if(y- 1 >= 0)
        board[x][y - 1] ? 0 : value++;//判断左边的格子是否为0 判断了2 3 4列
    if(y + 1 < 4)
        board[x][y + 1] ? 0 : value++;//判断右边的格子是否为0 判断了1 2 3列
    if(x - 1 >= 0 && y - 1 >= 0)
        board[x - 1][y - 1] ? 0 : value++;//判断左上角
    if(x - 1 >= 0 && y + 1 < 4)
        board[x - 1][y +1] ? 0 : value++;//判断右上角
    if(x + 1 < 4 && y - 1 >= 0)
        board[x + 1][y - 1] ? 0 : value++;//判断左下角
    if(x + 1 < 4 && y + 1 < 4)
        board[x + 1][y + 1] ? 0 : value++;//判断右下角
    return value;
}
//计算新出现的数字的位置与上次不在同一位置出现
void cnt_value(int* new_x,int* new_y){
    int max_x, max_y, x, y, value;
    int max = 0;
    max = cnt_one(*new_x, *new_y);
    for(x = 0; x < 4; x++){
        for(y = 0; y < 4; y++){
            if(!board[x][y]){//如果x,y对应的空格为空
                value = cnt_one(x, y);//优先选取周围空格最多的空格展示新数字
                if(value > max && old_x != x && old_y != y){//避免在同一位置反复出现新数字
                    *new_x = x;
                    *new_y = y;
                    old_x = x;
                    old_y = y;
                }
            }
        }
    }


//游戏结束
int game_over(void){
    move(10, 5);
    addstr("game over!");//显示字符串
    refresh();//刷新生效
    sleep(3);//暂停
    endwin();//退出curses模式
    exit(0);
}

main函数

int main(){

    init();
    play();
    endwin();//关闭 curses 模式

    return 0;
}

编译

#编译 gcc 2048.c -o 2048 -lcurses 
#执行 ./2048

ncurses库使用参考文章:

Linux curses库使用

Linux下curses函数库

代码参考:

C 语言实现 2048 游戏

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐