C語言之三字棋的實現及擴展
在我們學習完數組之后,我們完全可以利用數組相關知識來寫一個微小型的游戲,比如說今天所說的——三子棋。
大綱:
文件組成
實現
完整代碼展示
擴展
即:
一.文件組成:
在我們學習的過程中,我們要逐漸習慣多文件的書寫方式,也就是模塊化書寫。
在本文中,筆者分為了三個文件來寫,分別是:
1.game.h——實現游戲函數的聲明
2.game.c——游戲函數的實現
3.test.c —— 測試及游戲函數的調用
二.實現
0.文件的初始化
在這里我們分別在我們所創建的 test.c 和 game.c 包含我們的頭文件——game.h
1.菜單的實現
在菜單中,我們設置玩家可以選擇的模式,play and quit
以及,菜單怎么樣多次循環選擇,菜單的容錯處理。這里,我們利用 do-while 來實現。
#define _CRT_SECURE_NO_WARNINGS 1//加這一句話是因為筆者采用的是 VS 編譯器,為了防止一些不必要的錯誤出現 #include "game.h" void menu()//列出可供玩家選擇的模式 { printf("**************************************************************\n"); printf("***************** 1.play ****************\n"); printf("***************** 0.exit ****************\n"); printf("**************************************************************\n"); } void play() { } int main() { int input;//在這里,我們利用玩家選擇的模式來控制循環的終止 do { menu(); printf("請輸入你的選擇:"); scanf("%d", &input); switch (input) { case 1://play play(); break; case 0://退出 printf("歡迎下次再來!\n"); break; default://當玩家輸入了非法字符,讓其重新選擇 printf("輸入錯誤,請重新輸入!\n"); } } while (input);//當input為0時,停止循環 return 0; }
運行效果:
現在,我們的菜單已經做好了,接下來要做的就是來打印我們的棋盤。
2.棋盤的打印
這里我們把打印函數的聲明放在 game.h 文件里,把實現放在game.c 文件中。
在寫代碼之前,我們先來想一想在棋盤打印中,我們能不能直接打印空格——這肯定是不能的,因為這樣,我們在屏幕上什么都看不見 (≧∇≦)ノ
game.c:
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void InitBoard(char board[3][3], int row, int col)//棋盤初始化 { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = ' '; } } } void DisplayBoard(char board[3][3], int row, int col)//棋盤打印函數 { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf(" %c ", board[i][j]); if (j < col - 1) { printf("|");//分割列 } } printf("\n"); if (i < row - 1) { for (j = 0; j < 3; j++) { printf("---");//分割行 if (j < col - 1) { printf("|"); } } } printf("\n"); } }
注:
為了避免文章贅余,test.c 以及 game.h不再表示
運行結果:
但是,我們這么寫,會不會有問題?
值得注意的是,在這有人會把棋盤打印寫成這個樣子
void DisplayBoard(char board[3][3], int row, int col)//棋盤打印函數 { for (int i = 0; i < 3; i++) { printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); if (i < 2) { printf("---|---|---\n");//分割行 } } }
這樣,無非還是上面那個問題,代碼寫死,無法擴展
所以,我們在這利用宏來實現,棋盤的大小隨我們的宏來改變
因此在這我們給出頭文件的部分
#pragma once #include<stdio.h> #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);//棋盤打印函數
3.棋盤下子
1.玩家下子
在這里我們一共要注意幾點:
1.在下子之前,我們要判斷玩家所要下的位置是否在棋盤內
2.檢測玩家要下的位置是否已有了棋子
3.下子之后,檢查棋盤的輸贏狀況 (這個我們后面再說)
void PlayerMove(char board[ROW][COL], int row, int col)//玩家下棋 { int x, y; printf("玩家走:\n"); printf("請輸入你所要落子的坐標:"); scanf("%d%d", &x, &y); if (board[x - 1][y - 1]!=' ')//坐標被占用 { printf("該坐標已被占用,請重新下子!\n"); } else if (!((x > 0 && x <= row) && (y > 0 && y <= col))) { printf("該坐標為非法坐標,請重新輸入!\n");//坐標非法 } else { board[x - 1][y - 1] = '*';//玩家落子,暫時用 * 來表示 } }
2.電腦下子
再這里我們暫不深究,使用隨機函數來生成一個坐標來下子
void ComputerMove(char board[ROW][COL], int row, int col)//電腦下棋 { int x, y; printf("電腦走:"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#';//這里我們用 # 來表示電腦下棋 break; } } }
4.勝負的判定
在這里,我們用一個函數的返回值來表示輸贏的各個情況。
#-----電腦贏
*-----玩家贏
C-----繼續下子
F-----和局
int ISFULL(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1; } char ISWIN(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col - 2; j++) { //判斷橫行 if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] != ' ') return board[i][j]; //判斷主對角線 else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] != ' ') return board[i][j]; //判斷副對角線 else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 1 - i][j] != ' ') return board[col - 1 - i][j]; } } for (i = 0; i < row - 2; i++) { for (j = 0; j < col; j++) { //判斷豎行 if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] != ' ') return board[i][j]; } }
//判斷是否滿盤---放在最后是因為最后一步的判斷
if (1 == ISFULL(board, row, col))
{
return 'F';
}
return 'C'; }
到這,我們的三子棋似乎已經編完了,先看一下運行結果:
在這個過程中,我們會發現電腦下的特別快(當然,這跟我們的懶惰有關……)所以我們在電腦下的步驟中加一個Sleep()函數來延長電腦所用時間
已即,我們可以下一次子就清一下屏,這樣看起來比較舒服
所以
最后的代碼部分:

#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #include<Windows.h> #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);//判斷輸贏 int ISFULL(char board[ROW][COL], int row, int col);//判斷棋盤是否已滿

#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void InitBoard(char board[ROW][COL], int row, int col)//棋盤初始化 { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = ' '; } } } //void DisplayBoard(char board[3][3], int row, int col)//棋盤打印函數 //{ // for (int i = 0; i < 3; i++) // { // printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); // if (i < 2) // { // printf("---|---|---\n"); // } // } //} void DisplayBoard(char board[ROW][COL], int row, int col)//棋盤打印函數 { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf(" %c ", board[i][j]); if (j < col - 1) { printf("|");//分割列 } } printf("\n"); if (i < row - 1) { for (j = 0; j < 3; j++) { printf("---");//分割行 if (j < col - 1) { printf("|"); } } } printf("\n"); } } void PlayerMove(char board[ROW][COL], int row, int col)//玩家下棋 { int x, y; printf("玩家走:\n"); while (1) { printf("請輸入你所要落子的坐標:"); scanf("%d%d", &x, &y); if (!((x > 0 && x <= row) && (y > 0 && y <= col))) { printf("該坐標為非法坐標,請重新輸入!\n");//坐標非法 } else if (board[x - 1][y - 1] != ' ')//坐標被占用 { printf("該坐標已被占用,請重新下子!\n"); } else { board[x - 1][y - 1] = '*';//玩家落子,暫時用 * 來表示 break; } } system("cls"); } void ComputerMove(char board[ROW][COL], int row, int col)//電腦下棋 { int x, y; printf("電腦走:\n"); Sleep(1000); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#';//這里我們用 # 來表示電腦下棋 break; } } system("cls"); } int ISFULL(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1; } char ISWIN(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col - 2; j++) { //判斷橫行 if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] != ' ') return board[i][j]; //判斷主對角線 else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] != ' ') return board[i][j]; //判斷副對角線 else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 1 - i][j] != ' ') return board[col - 1 - i][j]; } } for (i = 0; i < row - 2; i++) { for (j = 0; j < col; j++) { //判斷豎行 if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] != ' ') return board[i][j]; } } //判斷是否滿盤---放在最后是因為最后一步的判斷 if (1 == ISFULL(board, row, col)) { return 'F'; } return 'C'; }

#define _CRT_SECURE_NO_WARNINGS 1//加這一句話是因為筆者采用的是 VS 編譯器,為了防止一些不必要的錯誤出現 #include "game.h" void menu()//列出可供玩家選擇的模式 { printf("**************************************************************\n"); printf("***************** 1.play ****************\n"); printf("***************** 0.exit ****************\n"); printf("**************************************************************\n"); } void play() { int ret; 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; } } switch (ret) { case 'F': printf("和局!\n"); system("pause"); system("cls"); break; case '#': printf("電腦贏!\n"); system("pause"); system("cls"); break; case '*': printf("玩家贏!\n"); system("pause"); system("cls"); break; default: break; } } int main() { srand((unsigned)time(NULL));//隨機種子的初始化 int input;//在這里,我們利用玩家選擇的模式來控制循環的終止 do { menu(); printf("請輸入你的選擇:"); scanf("%d", &input); switch (input) { case 1://play system("cls"); play(); break; case 0://退出 printf("歡迎下次再來!\n"); break; default://當玩家輸入了非法字符,讓其重新選擇 system("cls"); printf("輸入錯誤,請重新輸入!\n"); } } while (input);//當input為0時,停止循環 return 0; }
三.擴展
1.五子棋的實現
五子棋的實現僅僅只改變了判斷規則,其它方式都沒變。
判斷代碼:
char ISWIN(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < col; i++) { for (j = 0; j < col - 4; j++) { //判斷橫行 if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] == board[i][j + 3] && board[i][j] == board[i][j + 4] && board[i][j] != ' ') return board[i][j]; //判斷對角線 else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] == board[i + 3][j + 3] && board[i][j] == board[i + 4][j + 4] && board[i][j] != ' ') return board[i][j]; else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 4 - i][j + 3] == board[col - 2 - i][j + 1] && board[col - 5 - i][j + 4] == board[col - 2 - i][j + 1] && board[col - 1 - i][j] != ' ') return board[col - 1 - i][j]; } } for (i = 0; i < col - 4; i++) { for (j = 0; j < col ; j++) { //判斷豎行 if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] == board[i + 3][j] && board[i][j] == board[i + 4][j] && board[i][j] != ' ') return board[i][j]; } } //判斷是否滿盤 if (1 == ISFULL(board, row, col)) { return 'f'; } return 'c'; }
2.玩家對戰玩家
只需將電腦下子的部分,替換成玩家下子即可
完整五子棋-人人對戰代碼:

#pragma once #include <stdio.h> #include<stdlib.h> #include<time.h> #include<Windows.h> #define ROW 10 #define COL 10 //初始化棋盤 void InitBoard(char board[ROW][COL], int row, int col); //打印棋盤 void DisplayBoard(char board[ROW][COL], int row, int col); //玩家走 void Player1Move(char board[ROW][COL],int row, int col); //玩家2走 void Player2Move(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); //判斷棋盤是否已滿 int ISFULL(char board[ROW][COL], int row, int col);

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = ' '; } } } void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < col; i++) printf(" %d", i + 1); printf("\n"); for (i = 0; i < row; i++) { printf("%2d", i + 1); for (j = 0; j < col; j++) { printf(" %c ",board[i][j]); if (j < col - 1) printf("|"); } printf("\n"); //打印分割行 if (i < row - 1) { printf(" "); for (j = 0; j < col; j++) { printf("---"); if (j < col - 1) printf("|"); } printf("\n"); } } } void Player1Move(char board[ROW][COL], int row, int col) { int i = 0, j = 0; while (1) { printf("請輸入玩家1要下的棋的坐標:"); scanf("%d%d", &i, &j); //檢查是否越界 if (1 <= i && i <= row && j >= 1 && j <= col) { if (board[i-1][j-1] != ' ') printf("此坐標已被占用!\n"); else { board[i-1][j-1] = '*'; break; } } else printf("坐標非法訪問!\n"); } system("cls"); } void Player2Move(char board[ROW][COL], int row, int col) { int i = 0, j = 0; while (1) { printf("請輸入玩家2要下的棋的坐標:"); scanf("%d%d", &i, &j); //檢查是否越界 if (1 <= i && i <= row && j >= 1 && j <= col) { if (board[i - 1][j - 1] != ' ') printf("此坐標已被占用!\n"); else { board[i - 1][j - 1] = '#'; break; } } else printf("坐標非法訪問!\n"); } system("cls"); } //void ComputerMove(char board[ROW][COL], int row, int col) //{ // int i = 0, j = 0; // printf("電腦走:\n"); // while (1) // { // i = rand() % row; // j = rand() % col; // if (board[i][j] == ' ') // { // board[i][j] = '#'; // break; // } // } // Sleep(1000); // system("cls"); //} int ISFULL(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { if (board[i][j] == ' ') return 0; } } return 1; } char ISWIN(char board[ROW][COL], int row, int col) { int i = 0, j = 0; for (i = 0; i < col; i++) { for (j = 0; j < col - 4; j++) { //判斷橫行 if (board[i][j] == board[i][j + 1] && board[i][j] == board[i][j + 2] && board[i][j] == board[i][j + 3] && board[i][j] == board[i][j + 4] && board[i][j] != ' ') return board[i][j]; //判斷對角線 else if (board[i][j] == board[i + 1][j + 1] && board[i + 2][j + 2] == board[i][j] && board[i][j] == board[i + 3][j + 3] && board[i][j] == board[i + 4][j + 4] && board[i][j] != ' ') return board[i][j]; else if (board[col - 1 - i][j] == board[col - 2 - i][j + 1] && board[col - 3 - i][j + 2] == board[col - 1 - i][j] && board[col - 4 - i][j + 3] == board[col - 2 - i][j + 1] && board[col - 5 - i][j + 4] == board[col - 2 - i][j + 1] && board[col - 1 - i][j] != ' ') return board[col - 1 - i][j]; } } for (i = 0; i < col - 4; i++) { for (j = 0; j < col ; j++) { //判斷豎行 if (board[i][j] == board[i + 1][j] && board[i][j] == board[i + 2][j] && board[i][j] == board[i + 3][j] && board[i][j] == board[i + 4][j] && board[i][j] != ' ') return board[i][j]; } } //判斷是否滿盤 if (1 == ISFULL(board, row, col)) { return 'f'; } return 'c'; }

#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void menu() { printf("*********************************************\n"); printf("******* 1.play *********\n"); printf("******* 0.quit *********\n"); printf("*********************************************\n"); } int choice() { int input = 0; printf("請輸入你的選擇:"); scanf("%d", &input); return input; } void game() { char ret; char board[ROW][COL] = { 0 }; InitBoard(board, ROW, COL); DisplayBoard(board, ROW, COL); while (1) { Player1Move(board, ROW, COL); DisplayBoard(board, ROW, COL); ret = ISWIN(board, ROW, COL); { if (ret != 'c') break; } Player2Move(board, ROW, COL); DisplayBoard(board, ROW, COL); ret = ISWIN(board, ROW, COL); { if (ret != 'c') break; } } switch (ret) { case 'f': printf("和局!\n"); break; case '#': //printf("電腦贏!\n"); printf("玩家2贏!\n"); break; case '*': printf("玩家1贏!\n"); break; default: break; } } void play() { int input; do { menu(); input = choice(); system("cls"); switch (input) { case 1: game(); break; case 0: printf("謝謝使用,歡迎下次再來!\n"); break; default: system("cls"); printf("輸入錯誤,請重新輸入!\n"); break; } } while (input); } int main() { play(); return 0; }
3.電腦下棋的探究
對此我們的算法肯定是需要極大的改進的
建議:
1.不在用隨機函數代替機器大腦
2.電腦根據情況堵截(但是這樣的話,最終的情況多是平局)
所以:
在這,我們還仍需更多的努力!
4.將game.c轉為lib文件
然后出現:
此時我們會看到在我們的項目文件夾下的Debug文件夾下產生了:
接着,我們就可刪除我們項目中的game.c了
然后再test.c中加入這樣的一句話:
此時我們再點擊運行,發現與之前並無不同!
5.打包為可安裝文件
因為在VS2019中,微軟似乎刪除了這個組件,所以我們先需要去官網下載安裝一下 : 點這
直接安裝就行,盡管它是英文版,但是我們任可使用(學好英語的重要性!!!)
安裝好之后,
下面是具體步驟:
之后我們就可以把我們所編寫的游戲分享給我們的小伙伴,讓其安裝暢玩
關於三子棋的講解便到此為止。
因筆者水平有限,若有錯誤之處,還望多多指正。