C语言小游戏: 2048.c


概要:2048.c是一个C语言编写的2048游戏,本文将详细分析它的源码和实现。C语言是一种经典实用的编程语言,本身也不复杂,但是学会C语言和能够编写实用的程序还是有一道鸿沟的。本文试图通过一个例子展示如何用C语言实现一个简单但有用的程序。

一、程序简介

本文分析的代码是mevdschee在GitHub上的项目2048.c,游戏的规则和安装说明都可以到主页查看,本文不再赘述。顺便一提,这个程序虽然是纯C编写的,但是它适用于Linux终端,因此如果你想要看一下运行效果可能需要一个Linux.

2048.c源代码只有一个文件,也就是2048.c。它支持图形和色彩,右上角显示分数,下面是操作说明。界面整体看起来挺简洁美观,我们一会看一下它是怎么做到的。

二、代码结构

我们先看一下程序所包含的函数,大体了解它的结构和功能。

程序入口和测试:

  • main (argc,argv[])
  • test ()

绘制界面相关:

  • getColor (value,color,length)
  • drawBoard (board[][])
  • setBufferedInput (enable):设置终端的行为
  • signal_callback_handler (signum)

游戏逻辑:

  • findTarget (array[],x,stop)
  • slideArray (array[])
  • rotateBoard (board[][])
  • moveUp (board[][])
  • moveLeft (board[][])
  • moveDown (board[][])
  • moveRight (board[][])
  • findPairDown (board[][])
  • countEmpty (board[][])
  • gameEnded (board[][])
  • addRandom (board[][])
  • initBoard (board[][])

从函数的参数中可以看出,游戏使用的主要的数据结构是一个二维数组,在主函数中定义: uint8_t board[SIZE][SIZE] 。SIZE的值默认是4,这是2048游戏面板的一般大小,下文直接称为4。数组中的元素保存的是指数,例如如果显示的数是1024,那么存储的应该是10。在初始化过程中,该数组被填满0.

主函数中完成一些初始化和设置工作,然后进入主循环。在循环中接受用户的键盘输入,然后调用相应的函数。

三、图形绘制函数

 1 void drawBoard(uint8_t board[SIZE][SIZE]) {
 2     uint8_t x,y;
 3     char color[40], reset[] = "\033[m";
 4     printf("\033[H");
 5 
 6     printf("2048.c %17d pts\n\n",score);
 7 
 8     for (y=0;y) {
 9         for (x=0;x) {
10             getColor(board[x][y],color,40);
11             printf("%s",color);
12             printf("       ");
13             printf("%s",reset);
14         }
15         printf("\n");
16         for (x=0;x) {
17             getColor(board[x][y],color,40);
18             printf("%s",color);
19             if (board[x][y]!=0) {
20                 char s[8];
21                 snprintf(s,8,"%u",(uint32_t)1<<board[x][y]);
22                 uint8_t t = 7-strlen(s);
23                 printf("%*s%s%*s",t-t/2,"",s,t/2,"");
24             } else {
25                 printf("   ·   ");
26             }
27             printf("%s",reset);
28         }
29         printf("\n");
30         for (x=0;x) {
31             getColor(board[x][y],color,40);
32             printf("%s",color);
33             printf("       ");
34             printf("%s",reset);
35         }
36         printf("\n");
37     }
38     printf("\n");
39     printf("        ←,↑,→,↓ or q        \n");
40     printf("\033[A"); // one line up
41 }

在drawBoard函数中我们看到绘制的实现过程。函数的主体是一个for循环,每循环一次画一行,这里指的是Board中的一行。循环体中有3个小for循环,每个循环画出终端中的一行,也就是说Board的一行是终端的3行。每个格子的尺寸是3行7列,最中间的位置是数字,如果没有数字则输出一个点。其他区域则用空格填充。

细心的朋友可能已经发现,外循环的变量是y,内循环的变量为x,这样一来board[0][0]到board[3][0]表示的是第1行,board[0][1]到board[3][1]表示第2行,这种对应关系需要特别注意。

"\033m"之类的符号用于控制终端的颜色和其他一些行为。下面给出本程序中出现的用法,更多控制序列的用法可以参考这个网页。通过输出带颜色的空格和字符,2048.c在终端中实现了类似图形界面的效果。

\33[0m 关闭所有属性 
\33[30m -- \33[37m 设置前景色 
\33[40m -- \33[47m 设置背景色 
\33[nA 光标上移n行 
\33[nB 光标下移n行 
\33[nC 光标右移n行 
\33[nD 光标左移n行 
\33[y;xH设置光标位置 
\33[2J 清屏 
\33[?25l 隐藏光标 
\33[?25h 显示光标 

 四、游戏逻辑

我们现在已经知道游戏的主要数据结构,以及如何将它显示在屏幕上,我们接下来要关注游戏罗杰是怎么实现的。2048游戏本身非常简单,其实我们只想关心划的那一下是怎么实现的。我们已经看到2048.c实现了moveUp、moveLeft、moveDown、moveRight四个函数,表示4个划的方向。

moveUp函数看起来也非常简单,它仅仅调用4次slideArray函数。还记得刚刚说过的二维数组和盘面的对应规则吗,矩阵的每一行代表的是盘面的一列,因此每次滑动一个一维数组,实际上滑动的是一列。slideArray函数负责将数组从高index到低index滑动,对应在屏幕上,也就是向上滑动了。

1 bool moveUp(uint8_t board[SIZE][SIZE]) {
2     bool success = false;
3     uint8_t x;
4     for (x=0;x) {
5         success |= slideArray(board[x]);
6     }
7     return success;
8 }

slideArray函数和它的辅助函数findTarget任务已经比较简单明了,就不需要详细说了。需要注意的就是在滑的时候合并的块不能第二次合并了,例如2 2 2 2一次合并的结果是4 4,而不会是8.

其他几个函数实现比较巧妙,作者先把盘面进行旋转,然后再调用这个moveUp函数实现。作者通过rotateBoard函数把这个4x4的矩阵旋转90度。数组的下标可以通过建立坐标系得到。

 1 void rotateBoard(uint8_t board[SIZE][SIZE]) {
 2     uint8_t i,j,n=SIZE;
 3     uint8_t tmp;
 4     for (i=0; i2; i++) {
 5         for (j=i; j1; j++) {
 6             tmp = board[i][j];
 7             board[i][j] = board[j][n-i-1];
 8             board[j][n-i-1] = board[n-i-1][n-j-1];
 9             board[n-i-1][n-j-1] = board[n-j-1][i];
10             board[n-j-1][i] = tmp;
11         }
12     }
13 }

了解了这些信息,再看其他的函数比如countEmpty、addRandom等就非常简单了,大家直接去看代码就可以了。

五、总结

2048.c这个小游戏虽然只有400多行,但复现了2048游戏的精髓。而且程序以纯C语言实现,没有使用ncurses之类的第三方库,得到了很不错的效果。实现的过程也有一些精巧的地方,例如如何把问题化繁为简的,如何避免多次编写move函数。其实2048.c不仅可以拿来阅读,无聊的时候玩一局也是相当不错的。

相关