Linux控制台版本2048

来源:互联网 发布:mirrorlink 软件 编辑:程序博客网 时间:2024/05/23 01:04

在Github上看到一个荷兰人写的linux控制台版的2048,用的C语言,感觉很有意思。

原网址在这里。

读了一下他的源码,感觉写的不错,就厚着脸皮加了一些中文注释,源码如下:

/*   ============================================================================Name        : 2048.cAuthor      : Maurits van der ScheeDescription : Console version of the game "2048" for GNU/Linux============================================================================*Note by Zhengmingpei,ChinaTime:2014.10.13Contact:http://Zhengmingpei.github.comEmail:yueyawanbian@gmail.com*/#define _XOPEN_SOURCE 500#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <termios.h>#include <stdbool.h>#include <stdint.h>#include <time.h>#include <signal.h>#define SIZE 4uint32_t score=0;uint8_t scheme=0;// 根据value获取相应的颜色,将包含设置终端颜色的字符串复制给colorvoid getColor(uint16_t value, char *color, size_t length) {// 声明三个颜色数组,用一维数组,但每个奇数位和偶数位组成一个前后景色// 后两个数组分别对应程序的启动选项"blackwhite","bluered"uint8_t original[] = {8,255,1,255,2,255,3,255,4,255,5,255,6,255,7,255,9,0,10,0,11,0,12,0,13,0,14,0,255,0,255,0};uint8_t blackwhite[] = {232,255,234,255,236,255,238,255,240,255,242,255,244,255,246,0,248,0,249,0,250,0,251,0,252,0,253,0,254,0,255,0};uint8_t bluered[] = {235,255,63,255,57,255,93,255,129,255,165,255,201,255,200,255,199,255,198,255,197,255,196,255,196,255,196,255,196,255,196,255};uint8_t *schemes[] = {original,blackwhite,bluered};uint8_t *background = schemes[scheme]+0;uint8_t *foreground = schemes[scheme]+1;if (value > 0) while (value >>= 1)// value不断右移一位,直到值变为0,实现每个二进制一个不同的颜色{if (background+2<schemes[scheme]+sizeof(original)) {background+=2;foreground+=2;}}//linux下终端及字体颜色设置语句的字符串snprintf(color,length,"\033[38;5;%d;48;5;%dm",*foreground,*background);}// 绘制数据板,数据板共3×4行,7×4列void drawBoard(uint16_t board[SIZE][SIZE]) {int8_t x,y;// \033[m:关闭所有属性char color[40], reset[] = "\033[m";// \033[H:调整光标位置printf("\033[H");printf("2048.c %17d pts\n\n",score);//数据板共3×4行,7×4列for (y=0;y<SIZE;y++) {//首行打印空白for (x=0;x<SIZE;x++) {getColor(board[x][y],color,40);printf("%s",color);printf("       ");//reset 重置,避免对非数据板部分造成影响printf("%s",reset);}printf("\n");//次行打印数字,数字居中for (x=0;x<SIZE;x++) {getColor(board[x][y],color,40);printf("%s",color);if (board[x][y]!=0) {char s[8];//此处注意,是board[x][y]而不是yxsnprintf(s,8,"%u",board[x][y]);int8_t t = 7-strlen(s);printf("%*s%s%*s",t-t/2,"",s,t/2,"");} else {printf("   ·   ");}printf("%s",reset);}printf("\n");//末行打印空白for (x=0;x<SIZE;x++) {getColor(board[x][y],color,40);printf("%s",color);printf("       ");printf("%s",reset);}printf("\n");}printf("\n");printf("        ←,↑,→,↓ or q        \n");//疑似回车printf("\033[A");}// 查找一维数组中x左侧待合并数的坐标,stop为检查点int8_t findTarget(uint16_t array[SIZE],int8_t x,int8_t stop) {int8_t t;//若x为第一个数,左边无数,直接返回x if (x==0) {return x;}//遍历x左边的坐标for(t=x-1;t>=0;t--) {//合并算法://1.t处的数不为0且与x处的数不相等,返回t+1//2.t处的数不为0且与x处的数相等,返回t//3.t处的数为0,根据stop判断是否向前查找,防止多次合并if (array[t]!=0) {if (array[t]!=array[x]) {// merge is not possible, take next positionreturn t+1;}return t;} else {// we should not slide further, return this oneif (t==stop) {return t;}}}// we did not find areturn x;}//对一维数组进行移动bool slideArray(uint16_t array[SIZE]) {bool success = false;//声明当前位置,待合并的位置,检查点int8_t x,t,stop=0;for (x=0;x<SIZE;x++) {if (array[x]!=0) {t = findTarget(array,x,stop);// 如果待合并的位置与当前位置不相等,进行移动或者合并// if target is not original position, then move or mergeif (t!=x) {// 如果待合并的位置不是0,右移检查点stop// if target is not zero, set stop to avoid double mergeif (array[t]!=0) {score+=array[t]+array[x];stop = t+1;}array[t]+=array[x];array[x]=0;success = true;}}}return success;}//旋转数据板,向右旋转90度,这样可以用一个方向的数组移动间接控制四个方向的移动void rotateBoard(uint16_t board[SIZE][SIZE]) {int8_t i,j,n=SIZE;uint16_t tmp;//环形旋转,先外而内,先左后右for (i=0; i<n/2; i++){for (j=i; j<n-i-1; j++){tmp = board[i][j];board[i][j] = board[j][n-i-1];board[j][n-i-1] = board[n-i-1][n-j-1];board[n-i-1][n-j-1] = board[n-j-1][i];board[n-j-1][i] = tmp;}}}//向上移动数据板bool moveUp(uint16_t board[SIZE][SIZE]) {bool success = false;int8_t x;for (x=0;x<SIZE;x++) {//对每一列做移动或者合并处理,//这里是列而不是行,与前面的输出顺序有关success |= slideArray(board[x]);//只要有一列成功,就成功}return success;}// 左移:向右旋转90度,向上合并,再旋转3个90度bool moveLeft(uint16_t board[SIZE][SIZE]) {bool success;rotateBoard(board);success = moveUp(board);rotateBoard(board);rotateBoard(board);rotateBoard(board);return success;}// 下移:向右旋转2个90度,向上合并,再旋转2个90度bool moveDown(uint16_t board[SIZE][SIZE]) {bool success;rotateBoard(board);rotateBoard(board);success = moveUp(board);rotateBoard(board);rotateBoard(board);return success;}// 右移:向右旋转3个90度,向上合并,再旋转1个90度bool moveRight(uint16_t board[SIZE][SIZE]) {bool success;rotateBoard(board);rotateBoard(board);rotateBoard(board);success = moveUp(board);rotateBoard(board);return success;}bool findPairDown(uint16_t board[SIZE][SIZE]) {bool success = false;int8_t x,y;for (x=0;x<SIZE;x++) {for (y=0;y<SIZE-1;y++) {if (board[x][y]==board[x][y+1]) return true;}}return success;}// 计算数据板是否已满int16_t countEmpty(uint16_t board[SIZE][SIZE]) {int8_t x,y;int16_t count=0;for (x=0;x<SIZE;x++) {for (y=0;y<SIZE;y++) {if (board[x][y]==0) {count++;}}}return count;}// 检查游戏是否结束bool gameEnded(uint16_t board[SIZE][SIZE]) {bool ended = true;// 如果有空位,未结束if (countEmpty(board)>0) return false;// 横向检查,有相等相邻数,未结束if (findPairDown(board)) return false;rotateBoard(board);// 旋转一次,纵向检查,有相等相邻数,未结束if (findPairDown(board)) ended = false;rotateBoard(board);rotateBoard(board);rotateBoard(board);return ended;}// 随机重置数据板void addRandom(uint16_t board[SIZE][SIZE]) {// 全局变量,是否已初始化static bool initialized = false;// x,y 坐标int8_t x,y;// r 随机位置,len 所有为空的数据板数据长度int16_t r,len=0;// n 随机数据, list 所有为空的数据板位置uint16_t n,list[SIZE*SIZE][2];if (!initialized) {srand(time(NULL));initialized = true;}// 找出数据板上所有为空的坐标for (x=0;x<SIZE;x++) {for (y=0;y<SIZE;y++) {if (board[x][y]==0) {list[len][0]=x;list[len][1]=y;len++;}}}// 如果有为空的情况,才填充数据if (len>0) {r = rand()%len;x = list[r][0];y = list[r][1];n = ((rand()%10)/9+1)*2;board[x][y]=n;}}// 设置输入模式,在行缓冲和无缓冲中切换void setBufferedInput(bool enable) {static bool enabled = true;static struct termios old;struct termios new;if (enable && !enabled) {// restore the former settingstcsetattr(STDIN_FILENO,TCSANOW,&old);// set the new stateenabled = true;} else if (!enable && enabled) {// get the terminal settings for standard inputtcgetattr(STDIN_FILENO,&new);// we want to keep the old setting to restore them at the endold = new;// disable canonical mode (buffered i/o) and local echonew.c_lflag &=(~ICANON & ~ECHO);// set the new settings immediatelytcsetattr(STDIN_FILENO,TCSANOW,&new);// set the new stateenabled = false;}}int test() {uint16_t array[SIZE];uint16_t data[] = {0,0,0,2,2,0,0,0,0,0,2,2,4,0,0,0,0,2,0,2,4,0,0,0,2,0,0,2,4,0,0,0,2,0,2,0,4,0,0,0,2,2,2,0,4,2,0,0,2,0,2,2,4,2,0,0,2,2,0,2,4,2,0,0,2,2,2,2,4,4,0,0,4,4,2,2,8,4,0,0,2,2,4,4,4,8,0,0,8,0,2,2,8,4,0,0,4,0,2,2,4,4,0,0};uint16_t *in,*out;uint16_t t,tests;uint8_t i;bool success = true;tests = (sizeof(data)/sizeof(data[0]))/(2*SIZE);for (t=0;t<tests;t++) {in = data+t*2*SIZE;out = in + SIZE;for (i=0;i<SIZE;i++) {array[i] = in[i];}slideArray(array);for (i=0;i<SIZE;i++) {if (array[i] != out[i]) {success = false;}}if (success==false) {for (i=0;i<SIZE;i++) {printf("%d ",in[i]);}printf("=> ");for (i=0;i<SIZE;i++) {printf("%d ",array[i]);}printf("expected ");for (i=0;i<SIZE;i++) {printf("%d ",in[i]);}printf("=> ");for (i=0;i<SIZE;i++) {printf("%d ",out[i]);}printf("\n");break;}}if (success) {printf("All %u tests executed successfully\n",tests);}return !success;}void signal_callback_handler(int signum) {printf("         TERMINATED         \n");setBufferedInput(true);printf("\033[?25h");exit(signum);}int main(int argc, char *argv[]) {uint16_t board[SIZE][SIZE];char c;bool success;if (argc == 2 && strcmp(argv[1],"test")==0) {return test();}if (argc == 2 && strcmp(argv[1],"blackwhite")==0) {scheme = 1;}if (argc == 2 && strcmp(argv[1],"bluered")==0) {scheme = 2;}// 33[?25l 隐藏光标// 33[2J 清屏// 33[H 设置光标位置printf("\033[?25l\033[2J\033[H");// register signal handler for when ctrl-c is pressedsignal(SIGINT, signal_callback_handler);// 将数据清为0memset(board,0,sizeof(board));// 添加两次随机数,因为初始化时产生2个随机数addRandom(board);addRandom(board);// 绘制数据板drawBoard(board);// 禁用缓存输入,终端支持按字符读取且不回显setBufferedInput(false);// 游戏主循环while (true) {c=getchar();switch(c) {case 97:// 'a' keycase 104:// 'h' keycase 68:// left arrowsuccess = moveLeft(board);  break;case 100:// 'd' keycase 108:// 'l' keycase 67:// right arrowsuccess = moveRight(board); break;case 119:// 'w' keycase 107:// 'k' keycase 65:// up arrowsuccess = moveUp(board);    break;case 115:// 's' keycase 106:// 'j' keycase 66:// down arrowsuccess = moveDown(board);  break;default: success = false;}//合并成功,则重新绘制if (success) {drawBoard(board);usleep(150000);addRandom(board);drawBoard(board);if (gameEnded(board)) {printf("         GAME OVER          \n");break;}}// 如果输入是 q 的话,打开行缓冲,显示光标if (c=='q') {printf("        QUIT? (y/n)         \n");while (true) {c=getchar();if (c=='y'){setBufferedInput(true);printf("\033[?25h");exit(0);}else {drawBoard(board);break;}}}if (c=='r') {printf("       RESTART? (y/n)       \n");while (true) {c=getchar();if (c=='y'){memset(board,0,sizeof(board));addRandom(board);addRandom(board);drawBoard(board);break;}else {drawBoard(board);break;}}}}setBufferedInput(true);printf("\033[?25h");return EXIT_SUCCESS;}


 

1 0