C語言數(shù)組超詳細講解中篇三子棋
前言
本文主要是對前面所學(xué)內(nèi)容進行復(fù)習(xí)和練習(xí),學(xué)習(xí)內(nèi)容包括但不限于:
- 分支與循環(huán)語句
- 函數(shù)
- 數(shù)組
本文要通過編寫三子棋的游戲來進行知識點的再學(xué)習(xí)。
1、三子棋是什么?
1.1 百度百科
三子棋是黑白棋的一種。三子棋是一種民間傳統(tǒng)游戲,又叫九宮棋、圈圈叉叉、一條龍、井字棋等。將正方形對角線連起來,相對兩邊依次擺上三個雙方棋子,只要將自己的三個棋子走成一條線,對方就算輸了。但是,有很多時候會出現(xiàn)和棋的情況。

1.2 游戲編程準(zhǔn)備工作
通過觀察上圖,三子棋是下在一個井字形或者九宮格的棋盤內(nèi)的。因此,我們可以先從打印一個棋盤入手開始編寫程序。為了方便起見,我們規(guī)定輸出下面這樣的九宮格棋盤。

參照三子棋的下棋規(guī)則,制定初步的編程思路:
1、在玩游戲開始前輸出一些符號和文字,讓界面更加有儀式感,例如:
printf("*********************\n");
printf("*****1. 開始游戲*****\n");
printf("*****0. 退出游戲*****\n");
printf("*********************\n");
2、提示玩家選擇:
- 輸入1代表玩游戲
- 輸入0代表退出游戲
- 輸入其他,提示輸入錯誤,需要玩家重新輸入
3、下棋開始前先初始化棋盤,在打印出棋盤
4、玩家下棋后,再次打印出棋盤
5、電腦下棋后,再次打印出棋盤
6,如此循環(huán)往復(fù)幾步后,判斷贏家是誰,下棋結(jié)束
7、玩家可選擇繼續(xù)玩還是退出程序
2. 程序?qū)崿F(xiàn)
2.1 搭建程序框架
我們首先搭建程序框架,讓程序能夠跑起來,再接著修改程序,實現(xiàn)基本功能。
因為游戲是多次進行的,選擇do-while循環(huán)結(jié)構(gòu),編寫程序主題結(jié)構(gòu)。
void menu()
{//輸出提示字符
printf("*********************\n");
printf("*****1. 開始游戲*****\n");
printf("*****0. 退出游戲*****\n");
printf("*********************\n");
}
void play()
{//玩游戲的代碼
printf("玩家選擇玩游戲!\n");
}
int main()
{//利用do-while循環(huán),寫成主體結(jié)構(gòu)
int input = 0;
do
{
menu();//輸出提示信息
printf("請選擇1或者0==> ");//1代表玩,0代表退出游戲
scanf("%d", &input);//玩家輸入
switch (input)
{//通過分支結(jié)構(gòu),決定輸入是什么
case 1://玩游戲
play();//調(diào)用玩游戲的函數(shù)
break;
case 0://退出游戲
printf("退出游戲!\n");
break;
default://重新輸入
printf("輸入錯誤,請重新輸入1或0 \n");
break;
}//先進入循環(huán)輸出提示信息,當(dāng)輸入1時,滿足循環(huán)條件,接著執(zhí)行循環(huán)里面的程序,也就是玩游戲
//當(dāng)輸入0時,不滿足循環(huán)條件,也就是退出游戲
//使用do-while循環(huán)符合邏輯
} while (input);
return 0;
}
運行程序,分別輸入1 2 0,輸出結(jié)果如下所示,達到了預(yù)期的界面效果:
- 輸入1代表玩游戲
- 輸入其他,提示輸入錯誤,需要玩家重新輸入
- 輸入0代表退出游戲

2.2 模塊化編程
上面一小節(jié)的代碼搭建了程序的框架,能實現(xiàn)程序的基礎(chǔ)功能,我們接著進一步play函數(shù)中添加代碼,完善玩游戲的功能
由于代碼較多,將采用模塊化編程,這一使代碼可讀性更高一點。將不同功能的函數(shù)實現(xiàn)放在不同的源文件中。
2.2.1 源文件test.c
將函數(shù)main 、test、menu、play放入源文件test.c中,這里只是初步規(guī)劃,代碼會隨著實現(xiàn)功能慢慢增加。
//后續(xù)將會逐步添加,不表明只有這些
#include "play.h"http://頭文件必須添加,printf等函數(shù)正常工作
void menu()
{//輸出提示字符
printf("*********************\n");
printf("*****1. 開始游戲*****\n");
printf("*****0. 退出游戲*****\n");
printf("*********************\n");
}
void play()
{//玩游戲的具體實現(xiàn)代碼
printf("玩家選擇玩游戲!\n");
//存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
}
void test()
{//游戲界面代碼
int input = 0;
do
{
menu();
printf("請選擇1或者0==> ");
scanf("%d", &input);
switch (input)
{
case 1:
play();
break;
case 0:
printf("退出游戲!\n");
break;
default:
printf("輸入錯誤,請重新輸入1或0 \n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
2.2.2 源文件play.c
將函數(shù)初始化棋盤、打印棋盤、玩家下棋,等等放入源文件play.c,這里只是初步規(guī)劃,代碼會隨著實現(xiàn)功能慢慢增加。
//后續(xù)將會逐步添加,不表明只有這些
#include "play.h"http://頭文件必須添加,包含stdio.h,使printf等函數(shù)正常工作
//初始化棋盤
void InitBoard(char board[ROW][COL], int row, int col)
{
}
//打印棋盤
void DisplayBoard(char board[ROW][COL], int row, int col)
{
}
//玩家下棋
void PlayMover(char board[ROW][COL], int row, int col)
{
}
//電腦下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
}
2.2.3 頭文件play.h
將宏定義,函數(shù)聲明放在這個頭文件件play.h,這里只是初步規(guī)劃,代碼會隨著實現(xiàn)功能慢慢增加。
//后續(xù)將會逐步添加,不表明只有這些 #include<stdio.h>//添加頭文件 #define ROW 3//棋盤行數(shù) #define COL 3//棋盤列數(shù) void play(); //初始化棋盤 void InitBoard(char board[ROW][COL], int row, int col); //打印棋盤 void DisplayBoard(char board[ROW][COL], int row, int col); //玩家下棋 void PlayMover(char board[ROW][COL], int row, int col); //電腦下棋 void ComputerMove(char board[ROW][COL], int row, int col);
2.3 程序?qū)崿F(xiàn)—拓展play函數(shù)
2.3.1 棋盤初始化與打印函數(shù)
在玩家下棋前,需要完成兩件事情
- 使用函數(shù) InitBoard: 初始化棋盤,用空格初始化
- 使用函數(shù) DisplayBoard: 將棋盤打印出來。
//test.c
void play()
{
printf("玩家選擇玩游戲!\n");
//存放下棋的數(shù)據(jù) ROW代表棋盤行數(shù),COL代表棋盤列數(shù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
}
//play.c
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';//賦值空格,進行格式化
}
}
}
//第一稿
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{//此時數(shù)組里都是空格
printf("%c", board[i][j]);//都是空格看不見
}
printf("\n");
}
}
運行結(jié)果顯示,函數(shù)InitBoard使用空格完成初始化,但是空格直接打印都是空白,看不見,因此需要修改打印函數(shù)DisplayBoard

- 2.3.1.1 打印函數(shù)DisplayBoard——修改1
//第二稿
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//打印分隔符
printf("---|---|---\n");
}ruxiat
}

打印結(jié)果如上圖,基本上是一個九宮格棋盤了。但是第三行的字符是多余的,需要再次修改。
- 2.3.1.2 打印函數(shù)DisplayBoard——修改2
//第三稿
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{//但是這里有問題,每行的輸出是固定的3個,當(dāng)行列改變時,這里就不能用了
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//打印分隔符
if(i<ROW-1)//第三行不打印
printf("---|---|---\n");
}
}

運行結(jié)果如上所示。但是上面的代碼也存在問題,每行字符輸出是固定的3個,當(dāng)行列改變時,這里就會出問題。
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
將棋盤的行數(shù)、列數(shù)改為10 ,運行結(jié)果如下:

因此,這行代碼需要修改。
- 2.3.1.3 打印函數(shù)DisplayBoard——修改3
//第四稿
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{//將這里改為自動化輸出
printf(" %c ", board[i][j]);
printf("|");
}
printf("\n");
//打印分隔符
if (i < row - 1)//第三行不打印
printf("---|---|---\n");
}
}

上圖為顯示結(jié)果,第三列有問題,對代碼進行修改。
- 2.3.1.4 打印函數(shù)DisplayBoard——修改4
//第五稿
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if(j<col-1)第三列不打印
printf("|");
}
printf("\n");
//打印分隔符
if (i < row - 1)//第三行不打印
printf("---|---|---\n");//這里也有問題,寫成固定的
}
}

上面是運行結(jié)果,**程序代碼也存在問題,同前面一樣,輸出的字符是固定的,**因此再次修改代碼。
printf("---|---|---\n");//這里也有問題,寫成固定的
- 2.3.1.5 打印函數(shù)DisplayBoard——修改5
//第六稿,至此改完
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)第三列不打印
printf("|");
}
printf("\n");//每一行打完就換行
//打印分隔符
if (i < row - 1)//第三行不打印
{
for (int j = 0; j < col; j++)
{//此時可以測試10行10列了
printf("---");//純粹的符號打印,沒有輸入的字符
if (j < col - 1)//第三列不打印
printf("|");
}
printf("\n");//每一行打完就換行
}
}
}

運行結(jié)果如上圖。將棋盤行數(shù)、列數(shù)改為10,再次運行,結(jié)果見下圖,由此,打印函數(shù)DisplayBoard修改結(jié)束,符合使用要求了。

2.3.2 玩家下棋函數(shù) PlayMover
在初始化并打印棋盤后,接著就要邀請玩家下棋了,下完后也要打印出棋盤。在函數(shù) paly 里添加 PlayMover 和 DisplayBoard。
void play()
{
printf("玩家選擇玩游戲!\n");
//存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
//玩家下棋
PlayMover(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
}
玩家下棋函數(shù) PlayMover,根據(jù)玩家輸入的坐標(biāo)信息,在對應(yīng)的位置輸入*,代表落下棋子。
void PlayMover(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("游戲已開始,請玩家下棋\n");
while (1)
{
printf("請輸入落棋子位置 ==> ");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{//玩家的角度,行列都是1開始的
//下棋
if (board[x - 1][y - 1] == ' ')//數(shù)組角度,代碼要減1
{//落棋位置,就是前面打印棋盤 %c 的位置
board[x - 1][y - 1] = '*';//輸入字符,代表落下棋子
break;
}
else
{
printf("該位置已有棋子,請重新選擇落子位置\n");
}
}
else
{
printf("坐標(biāo)非法,請重新輸入\n");
}
}
}
輸入坐標(biāo)1 1,結(jié)果顯示:在對應(yīng)位置輸出*,代表落下棋子。由此表明,玩家下棋函數(shù) PlayMover符合預(yù)期效果。

2.3.3 電腦下棋函數(shù) ComputerMove
玩家下棋后,打印出棋盤,緊接著就輪到電腦下棋了,同樣打印出棋盤。在函數(shù) paly 里添加 ComputerMove 和 DisplayBoard。
void play()
{
printf("玩家選擇玩游戲!\n");
//存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
//玩家下棋
PlayMover(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
//電腦下棋
ComputerMove(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
}
電腦是自己隨機下棋的,電腦的行數(shù)、列數(shù)先隨機產(chǎn)生一個0-2的坐標(biāo)位置,再判斷這個位置是否為空:
- 為空,輸入字符#,代表電腦在此落下棋子
- 否則,會循環(huán)再次產(chǎn)生隨機數(shù),直到為空
下面將在源文件 play.c 中添加函數(shù) ComputerMove 的實現(xiàn)代碼,并在頭文件 play.h 中添加函數(shù)聲明。
//頭文件 play.h中
void ComputerMove(char board[ROW][COL], int row, int col);
//源文件 play.c中
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("電腦下棋 ==> \n");
while (1)
{//電腦下棋是隨機的
x = rand() % row;//輸出0-2之間的隨機數(shù) 0 1 2
y = rand() % col;//輸出0-2之間的隨機數(shù) 0 1 2
if (board[x][y]==' ')//先判斷對應(yīng)坐標(biāo)是否為空格
{//空格代表沒有棋子,則電腦將落下棋子
board[x][y] = '#';//#代表電腦下棋
break;//下完就跳出循環(huán)
}
}
}
由于坐標(biāo)位置隨機產(chǎn)生的,要在 頭文件 play.h 中添加兩個頭文件
#include <stdlib.h>//庫函數(shù) #include <time.h>//與系統(tǒng)時間有關(guān)的
在源文件 test.c 中的函數(shù)test中,添加函數(shù)srand,放在do-while循環(huán)之前,這樣電腦下棋的位置將是真正的隨機產(chǎn)生。
srand((unsigned int)time(NULL));

運行結(jié)果見上圖,由于下棋是雙方輪流下棋的,所以玩家下棋和電腦下棋是一個循環(huán)的動作。在此在函數(shù)play 添加while循環(huán):
void play()
{
printf("玩家選擇玩游戲!\n");
//存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
while (1)
{
//玩家下棋
PlayMover(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
//電腦下棋
ComputerMove(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
}
}

運行結(jié)果見上圖。到此,代碼已經(jīng)實現(xiàn)了正常的下棋功能并顯示出來。
2.2.4 判斷贏家函數(shù) WhoIsWin
三子棋棋盤一共只能下9個棋子,因此經(jīng)過雙方幾輪下棋后,勢必會分出勝負,結(jié)果分為三種:
- 玩家贏,約定返回 *
- 電腦贏,約定返回 #
- 平局,約定返回 q,此時棋盤已滿是棋子,不分勝負
- 其他情況,約定返回 c,繼續(xù)下棋
當(dāng)然,以上結(jié)果是不包括掀了棋盤的。
基于前面的代碼基礎(chǔ)上,在函數(shù) paly 里添加判斷贏家的函數(shù)WhoIsWin ,修改后的函數(shù) play邏輯是:
- 在while循環(huán)中,玩家先下棋
- 函數(shù) WhoIsWin 會判斷棋盤的結(jié)果,并以字符的方式返回,賦值給result
- if判斷result的值
(1)若返回的字符是c,則程序往下運行,輪到電腦下棋
(2)若返回的字符不是c,說明下棋出結(jié)果了,break直接跳出while循環(huán),進行剩下的3個if判斷
(3)返回 *,代表玩家贏; 返回 #,代表電腦贏;返回 c,代表平局;
void play()
{
printf("玩家選擇玩游戲!\n");
char result = 0;//棋盤最終結(jié)果根據(jù)result判斷
//存放下棋的數(shù)據(jù)
char board[ROW][COL] = { 0 };
//初始化棋盤為全空格
InitBoard(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
while (1)
{
//玩家下棋
PlayMover(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
result = WhoIsWin(board, ROW, COL);//函數(shù)判斷下棋結(jié)果后,返回對應(yīng)的字符
if (result !='c')//玩家下棋后進行一次判斷
{//不繼續(xù)下棋,說明已經(jīng)分出勝負了
break;
}
//電腦下棋
ComputerMove(board, ROW, COL);
//展示棋盤,打印
DisplayBoard(board, ROW, COL);
result = WhoIsWin(board, ROW, COL);//函數(shù)判斷下棋結(jié)果后,返回對應(yīng)的字符
if (result != 'c')//電腦下棋后進行一次判斷
{//不繼續(xù)下棋,說明已經(jīng)分出勝負了
break;
}
}
//根據(jù)函數(shù)WhoIsWin返回的字符,輸出下棋結(jié)果
if (result == '*')
{
printf("玩家贏了\n");
}
else if (result == '#')
{
printf("電腦贏了\n");
}
else
{
printf("平局\n");
}
}
在源文件 play.c 添加 WhoIsWin 的代碼,并在**頭文件 play.h **添加函數(shù)聲明:
//頭文件 play.h中 char WhoIsWin(char board[ROW][COL], int row, int col); int IfFull(char board[ROW][COL], int row, int col);
函數(shù) WhoIsWin 的邏輯:
- 判斷每一行、每一列、對角線上,三個棋子相同嗎?相同且不是空格,直接返回代表棋子的字符, *為玩家,#為電腦
- 棋盤棋子都是滿的,則代表平局,返回字符 q
- 其他情況,代表繼續(xù)下棋,還沒分出勝負,返回字符 c
//源文件 play.c中
//判斷棋盤是否滿了? 此函數(shù)包含在 WhoIsWin 中
int IfFull(char board[ROW][COL], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;//存在空格棋盤沒有滿
}
}
}
}
//判斷贏家
char WhoIsWin(char board[ROW][COL], int row, int col)
{
//判斷行,每一行連續(xù)三個棋子相同
for (int i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')
{//只要連續(xù)三個棋子相同,就直接返回這個棋子,這里不用區(qū)分* 還是#
return board[i][1];
}
}
//判斷列,每一列連續(xù)三個棋子相同
for (int j = 0; j < row; j++)
{
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
{
return board[1][j];
}
}
//對角線,連續(xù)三個棋子相同
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{//主對角線
return board[1][1];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
{//副對角線
return board[1][1];
}
//判斷平局,棋盤滿了,不分勝負
if (IfFull(board, row, col) == 1)
{
return 'q';//quit表示平局
}
//沒有贏家,棋盤也沒滿,繼續(xù)下棋
return 'c';//continue,表示繼續(xù)
}
運行結(jié)果見下圖,基本上一個完整的三子棋游戲代碼已經(jīng)實現(xiàn)了。
但是上述代碼還有一個小問題,在判斷每一行、每一列、對角線上,三個棋子是否相同時,代碼直接寫固定的3個,如果將棋盤改成5行5列的五子棋時,這里就會出現(xiàn)問題了。這里留到以后在修改成能適應(yīng)五子棋的游戲。三子棋的代碼實現(xiàn)就告一段落了。

完整代碼放入gitee中:
總結(jié)
本文主要是游戲三子棋的實現(xiàn),從零開始,介紹了代碼實現(xiàn)的思路,應(yīng)該從簡單的代碼實現(xiàn),然后再逐步添加代碼功能,及時打印結(jié)果進行調(diào)試,查看代碼是否達到預(yù)期要求,最終完善代碼。
代碼是從簡單代碼慢慢增加,刪減、優(yōu)化變成復(fù)雜的代碼,不可能是從上往下的模式去設(shè)計代碼,這不科學(xué)。要熟悉三子棋編寫代碼的流程,培養(yǎng)好的寫代碼的習(xí)慣。
到此這篇關(guān)于C語言數(shù)組超詳細講解中篇三子棋的文章就介紹到這了,更多相關(guān)C語言 三子棋內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C/C++百行代碼實現(xiàn)熱門游戲消消樂功能的示例代碼
這篇文章主要介紹了C/C++百行代碼實現(xiàn)熱門游戲消消樂功能的示例代碼,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

