1. 程式人生 > 實用技巧 >2048遊戲 - C語言不引入圖形庫簡單實現

2048遊戲 - C語言不引入圖形庫簡單實現

宣告:本程式絕大部分屬於原創,互動部分參考了部落格園 Judge Young的原創文章 遊戲2048原始碼 - C語言控制檯介面版,
作者Judge Young的演算法思想非常值得參考,感謝作者的分享
附上文章連結:https://www.cnblogs.com/judgeyoung/p/3760515.html

演算法總體思想:(請結合思維導圖觀看,移動合併演算法篇幅較大,部分放到函式註釋)

  1. 遊戲主體抽象:將遊戲數字面板抽象為一個二維陣列,0代表空格。
  2. 移動合併演算法:把每一行/列同等對待,只研究一行/列的移動和合並演算法,通過遍歷來實現所有行/列的移動合併演算法。
  3. 遊戲結束條件:分為三種情況,主動退出、遊戲失敗和遊戲勝利。
  4. 隨機數生成:尋找出空格,平均概率生成2 / 4。
  5. 介面顯示:利用清屏再列印,達到介面重新整理效果。
  6. 計分系統:遊戲結束時,將所有格子中數字相加得到最終分數。
  7. 矩陣翻轉:為了減少程式碼量與複用函式,提高練習效果,這裡犧牲了部分效率。
  8. 備註:移動合併演算法也可以通過矩陣翻轉90度達到更好的函式複用效果,但效率更低。
  9. 再備註:函式宣告處有一個撤回功能,可以利用old_squares做出來,但由於作者嫌麻煩所以沒有做。
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>

#define N	4				//矩陣大小為N * N
#define Max 2048			//勝利條件 

int squares[N][N];			//遊戲矩陣
int sum;					//遊戲積分
int old_squares[N][N];		//之前一步操作的矩陣 

void GameStart(void);		//遊戲開始介面
void Print(void);			//列印介面、計分
void Operator(char op);		//對使用者互動進行處理
int Operated(void);			//判斷矩陣是否發生變化 
void Copy(void);			//備份上一步的矩陣 
void MoveUp(int op);		//上移方格
void MoveLeft(int op);		//左移方塊 
void Transpose(int op);		//矩陣翻轉
int GameOver(char op);		//遊戲結束:失敗,退出和遊戲勝利(需設定條件)
int Win(void);				//判斷遊戲勝利 
//void Recall(void);		//撤回功能 
void Random(void);			//生成隨機數
int HasNull(void);			//判斷矩陣存在空白位置
void PrintSum(void);		//計算遊戲積分並列印

int main(void)
{
	char op = ' ';

	GameStart();
	Random();
	Operator(op);
	while (!GameOver(op)) {
		Copy();
		while (!Operated()) {
			op = getch();
			Operator(op);
		}
		Random();
		system("cls");
		Print();
	}
	system("pause");
	
	return 0;
}

//初始化遊戲引數
void GameStart(void) {
	int i, j;

	sum = 0;
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			squares[i][j] = 0;
			old_squares[i][j] = 0;
		}
	}
	
	printf("Press any key to start the game.\n");
}

//列印矩陣
void Print(void) {
	int i, j;

	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (squares[i][j] == 0) {
				printf(" \t");
				continue;
			}
			printf("%d\t", squares[i][j]);
		}
		printf("\n\n");
	}
	
	printf("\n");
	printf("(W)Up (S)Down (A)Left (D)Right\n");
	printf("    (Q)Quit (R)Recall\n\n");
}


void Operator(char op) {
	switch (op) {
	case 'w':
	case 'W':
		MoveUp(1); break;
	case 'a':
	case 'A':
		MoveLeft(1); break;
	case 's':
	case 'S':
		MoveUp(0); break;
	case 'd':
	case 'D':
		MoveLeft(0); break;
	default:
		break;
	}
}

int Operated(void) {
	int i, j;
	
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (old_squares[i][j] != squares[i][j])
				return 1;
		}
	}
	
	return 0;
}

void Copy(void) {
	int i, j;
	
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			old_squares[i][j] = squares[i][j];
		}
	}
}

//在一行中,使用兩個下標變數來遍歷列項,假設使用j和k,其中j總在k的後面,用來尋找k項後面第一個不為0的數字,而k項用於表示當前待比較的項,總是和j項之間隔著若干個數字0,或者乾脆緊挨著。
void MoveUp(int op) {
	int i, j, k;

	if (op == 0)
		Transpose(0);
	
	for (i = 0; i < N; i++) {
		for (j = 1, k = 0; j < 4; j++) {
			if (squares[j][i] > 0) { /* 找出k後面第一個不為空的項,下標為j,之後分三種情況 */
				//合併
				if (squares[k][i] == squares[j][i]) {
					squares[k][i] = 2 * squares[k][i];
					squares[j][i] = 0;
					k++;
				}
				//移動
				else if (squares[k][i] == 0) {
					squares[k][i] = squares[j][i];
					squares[j][i] = 0;
				}
				//碰撞
				else {
					squares[k + 1][i] = squares[j][i];
					if (j != k + 1) { /* 原先兩數不挨著 */
						squares[j][i] = 0;
					}
					k++;
				}
			}
		}
	}
	
	if (op == 0)
		Transpose(0);
}

void MoveLeft(int op) {
	int i, j, k;

	if (op == 0)
		Transpose(1);
	
	for (i = 0; i < N; i++) {
		for (j = 1, k = 0; j < 4; j++) {
			if (squares[i][j] > 0) {
				//合併
				if (squares[i][k] == squares[i][j]) {
					squares[i][k] = 2 * squares[i][k];
					squares[i][j] = 0;
					k++;
				}
				//移動
				else if (squares[i][k] == 0) {
					squares[i][k] = squares[i][j];
					squares[i][j] = 0;
				}
				//碰撞
				else {
					squares[i][k + 1] = squares[i][j];
					if (j != k + 1) { /* 原先兩數不挨著 */
						squares[i][j] = 0;
					}
					k++;
				}
			}
		}
	}
	
	if (op == 0)
		Transpose(1);
}

void Transpose(int op) {
	int i, j, temp = 0;

	//左右翻轉
	if (op == 1) {
		for (i = 0; i < N; i++) {
			for (j = 0; j < N / 2; j++) {
				temp = squares[i][j];
				squares[i][j] = squares[i][N - j - 1];
				squares[i][N - j - 1] = temp;
			}
		}
	}
	//上下翻轉
	else {
		for (i = 0; i < N; i++) {
			for (j = 0; j < N / 2; j++) {
				temp = squares[j][i];
				squares[j][i] = squares[N - j - 1][i];
				squares[N - j - 1][i] = temp;
			}
		}
	}
}

int GameOver(char op) {
	int i, j;

	if (op == 'q' || op == 'Q')
		printf("You have quit the game! GameOver!\n");
	else if (Win())
		printf("Congratulation! You win!\n");
	else {
		if (HasNull())
			return 0;
		for (i = 1; i < N; i++) {
			for (j = 0; j < N; j++) {
				//任意兩個相鄰的單元值相同,遊戲繼續
				if (squares[i - 1][j] == squares[i][j])
					return 0;
				if (squares[j][i - 1] == squares[j][i])
					return 0;
			}
		}
	}
	
	PrintSum();
	return 1;
}

int Win(void) {
	int i, j;
	
	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (squares[i][j] == Max)
				return 1;
		}
	}
	
	return 0;
}

//在矩陣中隨機位置生成數字2/4
void Random(void) {
	int sitex, sitey;

	sitex = rand() % 4;
	sitey = rand() % 4;
	while (squares[sitex][sitey] != 0) {
		sitex = rand() % 4;
		sitey = rand() % 4;
	}
	
	//生成2的概率是2/3,生成4的概率是1/3 
	if (rand() % 3 == 0)
		squares[sitex][sitey] = 4;
	else
		squares[sitex][sitey] = 2;
}

int HasNull(void) {
	int i, j;

	for (i = 0; i < N; i++) {
		for (j = 0; j < N; j++) {
			if (squares[i][j] == 0)
				return 1;
		}
	}
	
	return 0;
}

void PrintSum(void) {
	int i, j;

	for (i = 0; i < N; i++)
		for (j = 0; j < N; j++)
			sum += squares[i][j];
	
	printf("Game Over! Your total score is: %d! \n", sum);
}