1. 程式人生 > 其它 >C語言入門筆記 小遊戲開發 【三子棋】

C語言入門筆記 小遊戲開發 【三子棋】

技術標籤:C語言

目錄

零、遊戲規則

一、專案結構

二、整體邏輯和區域性函式分析

int main()

void test()

void menu()

void game()

game.h

void InitBoard(char board[ROW][COL],int row,int col)

void DisplayBoard(char board[ROW][COL], int row, int col)

void PlayerMove(char board[ROW][COL], int row, int col)

char IsWin(char board[ROW][COL], int row, int col)

void ComputerMove(char board[ROW][COL], int row, int col)

三、個人心得與感恩

四、專案檔案的分享


零、遊戲規則

三子棋是一種民間傳統遊戲,在九宮格的九個交點處,相對兩邊依次擺上三個雙方棋子,只要將自己的三個棋子走成一條線,即達成贏的條件。

一、專案結構

一共有三個檔案:

分別是

主檔案:game.cpp//寫函式的具體實現

標頭檔案:game.h//放置define常量和函式宣告

測試檔案:test.cpp//放入對應標頭檔案後,引用函式即可

二、整體邏輯和區域性函式分析

首先,我們先看test.cpp裡有哪些東西。

這裡面存放了主函式,

int main()

int main()
{
	test();
	return 0;
}

我們把整個遊戲的執行放在void test()函式中:

void test()

void test()
{
	int input;
	srand((unsigned int)time(NULL));
	printf("遊戲名:三子棋\n");
	printf("遊戲規則:在九宮格的九個交點處落子,由玩家和電腦交替下棋,某一方的三個棋子走成了一條線,則該方勝利。\n");
	printf("輸入格式:假如你想在第二行第一列放入你的棋子,則輸入:2 1,其中2和1用空格隔開。輸入完成後回車即可。\n");
	printf("代號說明:玩家端的棋子為:*;電腦端棋子為:#\n");
	do
	{
		menu();
		printf("請選擇: > ");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			cout << "三子棋"<<endl;
			printf("遊戲開始:\n");
			game();
			break;
		case 0:
			printf("退出成功。\n");
			break;
		default:
			printf("選擇有誤!請重新輸入0或1。\n");
			break;

		}
	} while (input);//選擇非0,1是繼續遊戲,其他的數是重新選擇,都滿足真的條件。
}

這是大體遊戲執行邏輯:

1、進入控制檯介面

2、輸入固定文字,用來描述遊戲名,遊戲規則等資訊

3、拿到使用者的一個輸入,根據輸入來進行三種判斷:進入遊戲,結束遊戲和重新輸入

這就是test函式乾的事情。

這裡會先呼叫一個選單函式,

void menu()

void menu()
{
	cout << "1.開始 0.退出\n";
}

用於指引玩家進行輸入;

當玩家選擇1的時候,這時會呼叫一個game函式來進入遊戲,所以接下來我們講game函式。

void game()

void game()
{
	char ret = 0;
	char board[ROW][COL] = {0};//最開始初始化最好是字元空格。
	//初始化棋盤
	InitBoard(board,ROW,COL);
	//列印棋盤
	DisplayBoard(board,ROW,COL);
	//下棋
	while (1)
	{
		//玩家下棋
		PlayerMove(board,ROW,COL);
		//玩家下完棋後展示新棋盤
		DisplayBoard(board, ROW, COL);
		//判斷玩家是否贏
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
		//電腦下棋
		ComputerMove(board, ROW, COL);
		//電腦下完棋後展示新棋盤
		DisplayBoard(board, ROW, COL);
		//判斷玩家是否贏
		ret = IsWin(board, ROW, COL);
		if (ret != 'C')
		{
			break;
		}
	}
	if (ret == '*')
	{
		printf("恭喜你,你贏了!\n");
	}
	else if (ret == '#')
	{
		printf("很遺憾,電腦贏了。\n");
	}
	else
	{
		printf("你和電腦打成了平局。\n");
	}
}

首先我們定義了一個字元變數ret,之後這個變數會用來存放遊戲繼續還是結束,玩家贏還是電腦贏的標誌。我們先將它初始化為0;

接著,我們建立了一個棋盤,這是一個數組,行數是ROW,列數是COL。

我們將ROW和COL作為一個define變數放在了game.h這個標頭檔案中,而之後的很多函式都需要宣告,這些宣告也在該標頭檔案中,所以我們先在test.cpp裡引入一個頭檔案:#include"game.h"

這裡先把game.h放出來:

game.h

#pragma once
//遊戲模組標頭檔案
#define ROW 3
#define COL 3

//宣告
void InitBoard(char board[ROW][COL], int row, int col);
void DisplayBoard(char board[ROW][COL], int row, int col);
void PlayerMove(char board[ROW][COL], int row, int col);
void ComputerMove(char board[ROW][COL], int row, int col);
char IsWin(char board[ROW][COL], int row, int col);
//告訴我們四種遊戲狀態:
//1、玩家贏 - '*'
//2、電腦贏 - '#'
//3、平局   - 'Q'
//4、繼續   - 'C'

int IsFull(char board[ROW][COL], int row, int col);
//返回1:表示棋盤滿了
//返回0:表示棋盤沒滿

接著,我們把這個3×3的具體陣列初始化。

這裡需要用到第一個函式:

棋盤初始化函式。

void InitBoard(char board[ROW][COL],int row,int col)

void InitBoard(char board[ROW][COL],int row,int col)//二維陣列不管是建立還是作為函式引數,不能省略列的大小
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			board[i][j] = ' ';
		}
	}
}

這個函式採用兩個for迴圈的方式,將陣列中的每一個元素初始化為了字元' ',空格。

初始化完成後,我們需要把棋盤打印出來。

void DisplayBoard(char board[ROW][COL], int row, int col)

void DisplayBoard(char board[ROW][COL], int row, int col)
{
	int i = 0;

	for (i = 0; i < row; i++)
	{
		//1.列印一行的資料
		int j = 0;
		for (j = 0; j < col; j++) 
		{
			if(j!=col-1)
			{
				printf(" %c |", board[i][j]);
			}
			else
			{
				printf(" %c \n", board[i][j]);
			}
		}
		//2.列印分割行
		if (i < row - 1)
		{
			int k = 0;
			for (k = 0; k < row; k++)
			{
				if (k != row - 1)
				{
					printf("---|");
				}
				else
				{
					printf("---\n");
				}
			}
		}	
	}
}

這個函式能打印出棋盤,具體邏輯如上。

棋盤樣式如下:

這時候,準備工作都做好了,我們開始正式下棋。

因為下棋直到某一方勝利才會停止,

所以我們寫一個while迴圈。

首先,玩家先下棋。

void PlayerMove(char board[ROW][COL], int row, int col)

void PlayerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("玩家走:>\n");
	while (1)
	{
		printf("請輸入要下的座標:>");
		scanf("%d%d", &x, &y);
		//判斷x,y座標的合法性
		if (x >= 1 && x <= row && y >= 1 && y <= row)
		{
			if (board[x - 1][y - 1] == ' ')
			{
				board[x - 1][y - 1] = '*';
				break;
			}	
			else
				printf("該座標被佔用!\n");
		}
		else
		{
			printf("座標非法,請重新輸入!\n");
		}
	}
}

先初始化橫縱座標x,y,

然後進行一個while迴圈,這個迴圈只有玩家輸入合法才會退出來。

座標合法性為:

x >= 1 && x <= row && y >= 1 && y <= row

如果格子裡是空格,也就是空的,就將這個陣列元素從''改成'*'即可。

如果格子裡不是空格,也就是已經有了'*'或者'#',說明已被佔用,所以輸出該座標被佔用,然後重新進行while迴圈即可。

如果輸入座標非法,比如一共是3×3的棋盤,輸入了橫座標為4,那麼就非法,還是重新進入while迴圈。

這裡有一個小問題,如果輸入的不是數字,而是字元,如何檢測並改善?

存疑,學到異常章節時回來解決。

下完棋後展示新的棋盤。

這時候判斷一下玩家是否贏,

我們需要用到一個結果函式:

char IsWin(char board[ROW][COL], int row, int col)

char IsWin(char board[ROW][COL], int row, int col)
{
	int i = 0;
	//行和列的判斷
	for (i = 0; i < row; i++)
	{
		if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
		{
			return board[i][1];
		}
	}
	for (i = 0; i < row; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
		{
			return board[1][i];
		}
	}
	//兩個對角線的判斷
	if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
		return board[1][1];
	if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
		return board[1][1];
	//平局的判斷
	if (1 == IsFull(board, ROW, COL))
	{
		return 'Q';
	}
	return 'C';
}

這個函式:

告訴我們四種遊戲狀態:
1、玩家贏 - '*'
2、電腦贏 - '#'
3、平局 - 'Q'
4、繼續 - 'C'

玩家贏的條件三個滿足其一即可:

某一行全為'*';

某一列全為'*';

兩個對角線中的一個全為'*';

判斷方式如上。

返回型別為這三個一樣的字元。

這樣寫的好處是,同樣這段程式碼可以判斷最後贏的是玩家還是電腦。

如果上述if全部不滿足,且棋盤滿了,

那麼就返回一個字元,告訴我們平局了。

如果玩家和電腦都沒贏,且棋盤沒滿,

那麼返回一個字元,告訴我們遊戲繼續。

玩家下完棋後,該電腦了。

void ComputerMove(char board[ROW][COL], int row, int col)

void ComputerMove(char board[ROW][COL], int row, int col)
{
	int x = 0;
	int y = 0;
	printf("電腦走:>\n");
	while (1)
	{
		x = rand() % row;//這樣x的值就只能是0,1,2,在範圍內
		y = rand() % col;
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

這裡,我們讓電腦隨機下在某一位置:

用產生的隨機數mod3即可。

而產生隨機數,我們需要呼叫rand();

呼叫rand(),我們要先呼叫srand();

因為rand的內部實現是用線性同餘法做的,他不是真的隨機數,只不過是因為其週期特別長,所以有一定的範圍裡可看成是隨機的,rand()會返回一隨機數值,範圍在0至RAND_MAX 間。

在呼叫此函式產生隨機數前,必須先利用srand()設好隨機數種子,如果未設隨機數種子,rand()在呼叫時會自動設隨機數種子為1。

rand ()產生的是假隨機數字,每次執行時是相同的。若要不同,以不同的值來初始化它.初始化的函式就是srand()。

srand((unsigned int)time(NULL));

這裡需要新增標頭檔案math.h和time.h,強制把時間戳轉換成無符號整型。

最後根據ret返回值來判斷是否繼續,不繼續了就退出迴圈。

然後輸出三種結果中的一種:玩家贏,電腦贏,平局。

此時game函式執行完畢,回到了test函式,break出了switch分支,繼續在do while裡迴圈,直到玩家輸入了0,程式才會結束。

三、個人心得與感恩

這款遊戲是我重新學習C語言來的第一個專案,我跟的是位元鵬哥的C語言教學,他把去年的C語言入門學習視訊分享到了B站並免費給我們學習。我已經學習了快40個小時的C語言課程了,也快過半,鵬哥是我見過C語言講的最明白的老師。之前我在還沒學習陣列時,自己寫演算法遇到的問題,後來他講陣列時都講了,一針見血。雖然鵬哥極大概率看不到這段話,但是我真的很感謝鵬哥您,您讓我重拾了學習語言的信心。我在大二那段時間學習了java,因為自己的一些問題導致對語言產生了反感的態度,但是現在,學完遞迴和兩個經典演算法問題後,我對底層演算法產生了興趣,想著如何用學過的資料結構對演算法進行優化。我也會一絲不苟地學完您的C語言課程。

對於同樣具有程式設計愛好的朋友,我建議你學完一部分基礎後,試著去寫一個像本章三子棋的小遊戲專案,寫完後,再根據程式碼一塊兒一塊兒地理一下它的邏輯,這對語言的理解會有很大的提升。

四、專案檔案的分享

我會先系統的學習一下github和git的用法,然後把本專案分享到我的github上。