Linux下使用readline库实现2048游戏

一、背景

上一篇文章介绍了readline的基本用法,这一篇通过readline读取按键对2048游戏的c语言实现;

二、思路

2.1 游戏介绍

关于2048游戏,网上找了一下介绍,你可以点击这里在线进行体验:

"《2048》的初始数字则是由2+2组成的基数4。在操作方面的不同则表现为一步一格的移动,变成更为爽快的一次到底。

相同数字的方况在靠拢、相撞时会相加。系统给予的数字方块不是2就是4,玩家要想办法在这小小的16格范围中凑出「2048」这个数字方块。"

    看似简单的4x4方格游戏,梳理规则的时候发现还是比较繁琐的;

2.2 移动、合并规则

    以向右移动为例,向右移动时,按照行(row)为单位各自进行处理:
  1. 移动完成后,需要保持有数字的方块都移动到最右侧,.eg: (0204)->(0024) / (2004)->(0024) / (2400)->(0024);
  2. 相邻的同等值方块进行一次合并动作,.eg: (0022)->(0004) / (0222) -> (0024) / (2222) -> (0044) / (2244) -> (0048);  
  3. 相邻的意思为非零方块的相邻,即中间夹杂的零方块还需进行移动消除,.eg:(0202) -> (0004) / (2024) -> (0044);
  4. 移动、合并之后,在最左列某个零方块上产生一个2或4的新值,其中2出现的概率是4的两倍;

2.3 胜利、失败的界定

胜利则查找数组,若出现某个方格值大于等于2048则表示胜利;

失败的界定得符合以下两个条件:

  1. 4x4 数组方格都被填满,没有零值方格;
  2. 4x4 数组方格无法再相互合并,即相邻方块值各不相等;

3 实现

    参考了文章[1]的思路,通过另外一个切入点,根据第二小节的描述,对移动、合并画出处理流程图如下:



开始代码的实现说明,先是定义了一个4x4的全局数组;

#define SIZE 4

static int grid[SIZE][SIZE] = { 
    {0,0,0,0},
    {0,0,0,0},
    {0,0,0,0},
    {0,0,0,0},
};

定义了通用的移动、合并的宏,方便后续操作;

#define __move_a2b(a, b) if ( (a) != 0 ) { \
    printf("Move  %d to %d\n", (a), (b)); \
    (b) = (a); \
    (a) = 0; \
}

#define __merge_a2b(a, b) if ( (b) != 0 ) { \
    printf("Merge %d to %d\n", (a), (b)); \
    (b) *= 2; \
    (a) = 0; \
}

#define __try_merge_a2b(a, b) if ( (b) == (a) ) { \
    __merge_a2b(a, b); \
}

然后是对应流程图的右移函数的实现,(左移、上移、下移类似)

#define __for_each(i, n) for ( i = 0, n = random() % SIZE; i < SIZE; i++, n = (n + 1) % SIZE )

int grid_move_right()
{   
    int ix = 0, jx = 0, row = 0, col = 0;
    printf("right\n");
    
    /* Move */
    for ( row = 0; row < SIZE; row++ ) {
        for ( col = SIZE - 1; col > 0; col-- ) {
            /* Fill zero by pulling */
            for ( ix = 0; ix < col && grid[row][col] == 0; ix++ ) {
                for ( jx = col; jx > 0; jx-- ) {
                    __move_a2b(grid[row][jx - 1], grid[row][jx]);
                }
            }
            for ( ix = 0; ix < col - 1 && grid[row][col - 1] == 0; ix++ ) {
                for ( jx = col - 1; jx > 0; jx-- ) {
                    __move_a2b(grid[row][jx - 1], grid[row][jx]);
                }
            }
            __try_merge_a2b(grid[row][col - 1], grid[row][col]);
        }
    }
    
    /* Generate one random cells in col[0] */
    if ( is_won() || is_over() ) {
        return;
    }

    __for_each(ix, row) {
        if ( grid[row][0] == 0 ) {
            grid[row][0] = __new_cell();
            break;
        }
    }
    grid_display(row, 0);
}


对于胜利失败的界定函数,根据需求描述进行实现:

int is_won()
{
    int ix = 0, jx = 0;

    for ( ix = 0; ix < SIZE; ix++ ) {
        for ( jx = 0; jx < SIZE; jx++ ) {
            if ( grid[ix][jx] >= 2048 ) {
                grid_display(ix, jx);
                printf("[ You won! ]\n");
                return 1;
            }
        }
    }
    return 0;
}

int is_over()
{
    int ix = 0, jx = 0;

    for ( ix = 0; ix < SIZE; ix++ ) {
        for ( jx = 0; jx < SIZE; jx++ ) {
            if ( ix < SIZE - 1 && grid[ix][jx] == grid[ix + 1][jx] ) {
                return 0;
            }
            if ( jx < SIZE - 1 && grid[ix][jx] == grid[ix][jx + 1] ) {
                return 0;
            }
            if ( grid[ix][jx] == 0 ) {
                return 0;
            }
        }
    }
    printf("[ Game over! ]\n");
    return 1;
}
如果游戏还没结束,那么还得产生一个新的随机块,随机函数实现如下:

/* 66.66% is 2; 33.33% is 4 */
#define __new_cell() \
    (random() & 0x3 ? 2 : 4)

然后对应的显示函数,为了显示方便,对新产生的方块进行黄颜色标记处理

char *grid_generate(char *buffer, size_t size, int row, int col)
{
    int ix = 0;
    int jx = 0;
    int len = 0;

#define __do_append(buffer, len, size, args...) do { \
    len += snprintf(buffer + len, size - len, args); \
} while(0);

    __do_append(buffer, len, size, "+----+----+----+----+\n");

    for ( ix = 0; ix < SIZE; ix++ ) {
        for ( jx = 0; jx < SIZE; jx++ ) {
            if ( grid[ix][jx] ) {
                if ( ix == row && jx == col ) {
                    __do_append(buffer, len, size, "|\033[40;33m%4d\033[0m", grid[ix][jx]);
                }
                else {
                    __do_append(buffer, len, size, "|%4d", grid[ix][jx]);
                }
            }
            else {
                __do_append(buffer, len, size, "|    ");
            }
        }
        __do_append(buffer, len, size, "|\n");
        __do_append(buffer, len, size, "+----+----+----+----+\n");
    }
    return buffer;
}

void grid_display(int row, int col)
{
    char line[2048] = {0};
    fprintf(stderr, "%s", grid_generate(line, sizeof(line), row, col));
}

核心函数讲完了,最后说一下主函数,本例使用的readline库rl_bin_key()函数对按键进行绑定操作,

上下左右分别为(WSAD、KJHL),绑方向键的方法还没有找到;

// gcc -g -o 2048 2048.c -lreadline
int main(int argc, char *argv[])
{
    char *pline = NULL;
    char prompt[2048] = {0};
    grid_init();

    rl_bind_key('h', grid_move_left);
    rl_bind_key('j', grid_move_up);
    rl_bind_key('k', grid_move_down);
    rl_bind_key('l', grid_move_right);

    rl_bind_key('d', grid_move_left);
    rl_bind_key('w', grid_move_up);
    rl_bind_key('s', grid_move_down);
    rl_bind_key('a', grid_move_right);

    while ( 1 ) {
        pline = readline("");
        if ( !pline ) {
            break;
        }
        free(pline);
    }
    return EXIT_SUCCESS;
}

四、结果分析

运行成功、失败的结果如下:

      

上手发现一次成功的难度较大,需后续增加回滚功能减低难度,要不一步走错基本gg

然后在显示方面可以优化,比如说每次都清屏再重新打印,并增加一些提示功能...

工程源码下载


参考文章:

[1] http://www.cnblogs.com/judgeyoung/p/3760515.html

[2] https://github.com/gabrielecirulli/2048


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值