在Github上看到一個荷蘭人寫的linux控制檯版的2048,用的C語言,感覺很有意思。
原網址在這裏。
讀了一下他的源碼,感覺寫的不錯,就厚着臉皮加了一些中文註釋,源碼如下:
/*
============================================================================
Name : 2048.c
Author : Maurits van der Schee
Description : Console version of the game "2048" for GNU/Linux
============================================================================
*
Note by Zhengmingpei,China
Time:2014.10.13
Contact:http://Zhengmingpei.github.com
Email:[email protected]
*/
#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 4
uint32_t score=0;
uint8_t scheme=0;
// 根據value獲取相應的顏色,將包含設置終端顏色的字符串複製給color
void 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]而不是yx
snprintf(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 position
return t+1;
}
return t;
} else {
// we should not slide further, return this one
if (t==stop) {
return t;
}
}
}
// we did not find a
return 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 merge
if (t!=x) {
// 如果待合併的位置不是0,右移檢查點stop
// if target is not zero, set stop to avoid double merge
if (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 settings
tcsetattr(STDIN_FILENO,TCSANOW,&old);
// set the new state
enabled = true;
} else if (!enable && enabled) {
// get the terminal settings for standard input
tcgetattr(STDIN_FILENO,&new);
// we want to keep the old setting to restore them at the end
old = new;
// disable canonical mode (buffered i/o) and local echo
new.c_lflag &=(~ICANON & ~ECHO);
// set the new settings immediately
tcsetattr(STDIN_FILENO,TCSANOW,&new);
// set the new state
enabled = 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 pressed
signal(SIGINT, signal_callback_handler);
// 將數據清爲0
memset(board,0,sizeof(board));
// 添加兩次隨機數,因爲初始化時產生2個隨機數
addRandom(board);
addRandom(board);
// 繪製數據板
drawBoard(board);
// 禁用緩存輸入,終端支持按字符讀取且不回顯
setBufferedInput(false);
// 遊戲主循環
while (true) {
c=getchar();
switch(c) {
case 97: // 'a' key
case 104: // 'h' key
case 68: // left arrow
success = moveLeft(board); break;
case 100: // 'd' key
case 108: // 'l' key
case 67: // right arrow
success = moveRight(board); break;
case 119: // 'w' key
case 107: // 'k' key
case 65: // up arrow
success = moveUp(board); break;
case 115: // 's' key
case 106: // 'j' key
case 66: // down arrow
success = 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;
}