C語言入門筆記 小遊戲開發 【三子棋】
技術標籤:C語言
目錄
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上。