C語言實現(xiàn)五子棋功能全解析
1、game.h
game.h:自定義頭文件,用于:
- 庫函數(shù)頭文件的包含
- 符號與結(jié)構(gòu)的聲明
- 函數(shù)的定義
//防止頭文件被重復包含 #pragma once //頭文件的包含 #include<stdio.h> #include<stdlib.h> #include<time.h> //符號的定義:使棋盤的大小可以跟著row和col的改變而改變 #define ROW 5 #define COL 5 //函數(shù)的聲明 //棋盤初始化 void BoardInit(char arr[ROW][COL], int row, int col); //打印棋盤 void BoardPrint(char arr[ROW][COL], int row, int col); //玩家下棋 void PlayerMove(char arr[ROW][COL], int row, int col); //電腦下棋 void ComputerMove(char arr[ROW][COL], int row, int col); //判斷輸贏 char IsWin(char arr[ROW][COL], int row, int col); //判斷棋盤是否滿了 int IsFull(char board[ROW][COL], int row, int col);
2、test.c
test.c:用于游戲邏輯的測試
#define _CRT_SECURE_NO_WARNINGS 1
//自定義頭文件的包含
#include"game.h"
void menu()
{
printf("================================\n");
printf("========= 1. play ==========\n");
printf("========= 0. exit ==========\n");
printf("================================\n");
}
//游戲邏輯的實現(xiàn)
void game()
{
//定義一個二維數(shù)組來存儲下棋的數(shù)據(jù)
char arr[ROW][COL] = { 0 };
//棋盤初始化
BoardInit(arr, ROW, COL);
//打印棋盤
BoardPrint(arr, ROW, COL);
char ch = 0;
while (1)
{
//玩家下棋
PlayerMove(arr, ROW, COL);
//打印棋盤
BoardPrint(arr, ROW, COL);
//判斷輸贏
ch = IsWin(arr, ROW, COL);
if (ch != 'C')
break;
//電腦下棋
ComputerMove(arr, ROW, COL);
//打印棋盤
BoardPrint(arr, ROW, COL);
//判斷輸贏
ch = IsWin(arr, ROW, COL);
if (ch != 'C')
break;
}
if (ch == '*')
printf("直接拿下!\n");
else if (ch == '#')
printf("你竟然打不過人機!\n");
else
printf("平局,得加油啊!\n");
}
int main()
{
int input = 0;
//設(shè)置隨機數(shù)種子
srand((unsigned int)time(NULL));
do {
//菜單
menu();
printf("請選擇:>");
scanf("%d", &input);
switch (input) {
case 1:
//玩游戲
game();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("輸入錯誤,請重新輸入!\n");
break;
}
} while (input);
return 0;
}3、game.c
game.c:游戲功能的實現(xiàn)
#define _CRT_SECURE_NO_WARNINGS 1
//自定義頭文件的包含
#include"game.h"
//函數(shù)的定義
//棋盤初始化
void BoardInit(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ' ';
}
}
}
//打印棋盤
void BoardPrint(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
//打印分割豎向分割
for (j = 0; j < col; j++)
{
printf(" %c ", arr[i][j]);
if (j < col - 1)
printf("|");
}
//一行完畢之后打印分隔符
printf("\n");
//打印橫向分割
if (i < row - 1) //最后一行不打印橫線分隔符
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
//一行完畢之后打印分隔符
printf("\n");
}
}
//玩家下棋
void PlayerMove(char arr[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))
{
//把玩家坐標對應數(shù)組下標
x -= 1;
y -= 1;
//判斷坐標是否被占用
if (arr[x][y] == ' ')
{
arr[x][y] = '*'; //假設(shè)玩家為*號
break;
}
else
{
printf("該坐標已被占用\n");
}
}
else
{
printf("坐標非法\n");
}
}
}
//電腦下棋
void ComputerMove(char arr[ROW][COL], int row, int col)
{
printf("電腦下棋\n");
while (1)
{
//在主函數(shù)生成種子srand
//隨機生成范圍內(nèi)的坐標
int x = rand() % row;
int y = rand() % col;
//判斷坐標是否被占用
if (arr[x][y] == ' ')
{
arr[x][y] = '#'; //假設(shè)電腦為#號
break;
}
}
}
//判斷輸贏
char IsWin(char board[ROW][COL], int row, int col)
{
/*
* 約定返回*代表玩家贏
* 返回#代表電腦贏
* 返回D代表平局
* 返回C代表繼續(xù)
*/
int i = 0;
int j = 0;
//判斷行
for (i = 0; i < row; i++)
{
int count = 0; //標記相同棋子的個數(shù)
for (j = 0; j < col - 1; j++)
{
if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
count++;
}
if (count == col - 1) //一次判斷有兩個棋子
return board[i][j];
}
//判斷列
for (i = 0; i < col; i++)
{
int count = 0;
for (j = 0; j < row - 1; j++)
{
if (board[j][i] == board[j + 1][i] && board[j][i] != ' ')
{
count++;
}
}
if (count == row - 1)
return board[j][i];
}
//判斷兩條斜邊
//第一條
int count = 0;
for (i = 0, j = 0; i < row - 1 && j < col - 1; i++, j++)
{
if (board[i][j] == board[i + 1][j + 1] && board[i][j] != ' ')
count++;
}
if (count == row - 1)
return board[i][j];
//第二條
count = 0; //把count重新置為0(易錯)
//注意:這里i+1,j-1,所以i小于row-1,j>0,而不是i<row,j>=0(易錯)
for (i = 0, j = col - 1; i < row - 1 && j > 0; i++, j--)
{
if (board[i][j] == board[i + 1][j - 1] && board[i][j] != ' ')
count++;
}
if (count == row - 1)
return board[i][j];
//判斷棋盤是否滿了
if (IsFull(board, row, col))
{
return 'D';
}
//如果上述情況都沒有返回,游戲繼續(xù)
return 'C';
}
//判斷棋盤是否滿了
if (IsFull(board, row, col))
{
return 'D';
}
//如果上述情況都沒有返回,游戲繼續(xù)
return 'C';
}
//判斷棋盤是否滿了
int IsFull(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++)
{
if (board[i][j] == ' ')
return 0; //有空格就返回0
}
}
return 1;
}4、游戲功能詳解
(1)、棋盤初始化
void BoardInit(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
arr[i][j] = ' ';
}
}
}
(2)、棋盤的打印
void BoardPrint(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
//打印分割豎向分割
for (j = 0; j < col; j++)
{
printf(" %c ", arr[i][j]);
if (j < col - 1)
printf("|");
}
//一行完畢之后打印分隔符
printf("\n");
//打印橫向分割
if (i < row - 1) //最后一行不打印橫線分隔符
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
}
//一行完畢之后打印分隔符
printf("\n");
}
}
(3)、玩家下棋
void PlayerMove(char arr[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))
{
//把玩家坐標對應數(shù)組下標
x -= 1;
y -= 1;
//判斷坐標是否被占用
if (arr[x][y] == ' ')
{
arr[x][y] = '*'; //假設(shè)玩家為*號
break;
}
else
{
printf("該坐標已被占用\n");
}
}
else
{
printf("坐標非法\n");
}
}
}
(4)、電腦下棋
void ComputerMove(char arr[ROW][COL], int row, int col)
{
printf("電腦下棋\n");
while (1)
{
//在主函數(shù)生成種子srand
//隨機生成范圍內(nèi)的坐標
int x = rand() % row;
int y = rand() % col;
//判斷坐標是否被占用
if (arr[x][y] == ' ')
{
arr[x][y] = '#'; //假設(shè)電腦為#號
break;
}
}
}
(5)、判斷游戲輸贏
char IsWin(char board[ROW][COL], int row, int col)
{
/*
* 約定返回*代表玩家贏
* 返回#代表電腦贏
* 返回D代表平局
* 返回C代表繼續(xù)
*/
int i = 0;
int j = 0;
//判斷行
for (i = 0; i < row; i++)
{
int count = 0; //標記相同棋子的個數(shù)
for (j = 0; j < col - 1; j++)
{
if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')
count++;
}
if (count == col - 1) //一次判斷有兩個棋子
return board[i][j];
}
//判斷列
for (i = 0; i < col; i++)
{
int count = 0;
for (j = 0; j < row - 1; j++)
{
if (board[j][i] == board[j + 1][i] && board[j][i] != ' ')
{
count++;
}
}
if (count == row - 1)
return board[j][i];
}
//判斷兩條斜邊
//第一條
int count = 0;
for (i = 0, j = 0; i < row - 1 && j < col - 1; i++, j++)
{
if (board[i][j] == board[i + 1][j + 1] && board[i][j] != ' ')
count++;
}
if (count == row - 1)
return board[i][j];
//第二條
count = 0; //把count重新置為0(易錯)
//注意:這里i+1,j-1,所以i小于row-1,j>0,而不是i<row,j>=0(易錯)
for (i = 0, j = col - 1; i < row - 1 && j > 0; i++, j--)
{
if (board[i][j] == board[i + 1][j - 1] && board[i][j] != ' ')
count++;
}
if (count == row - 1)
return board[i][j];
//判斷棋盤是否滿了
if (IsFull(board, row, col))
{
return 'D';
}
//如果上述情況都沒有返回,游戲繼續(xù)
return 'C';
}
//判斷棋盤是否滿了
if (IsFull(board, row, col))
{
return 'D';
}
//如果上述情況都沒有返回,游戲繼續(xù)
return 'C';
}(6)、判斷棋盤是否滿了
int IsFull(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++)
{
if (board[i][j] == ' ')
return 0; //有空格就返回0
}
}
return 1;
}5、AI算法下棋
大家可以發(fā)現(xiàn),在上面的代碼中,電腦下棋是非常笨拙的,因為電腦產(chǎn)生的坐標是隨機的,即不會攔截玩家,也不會判斷自己,所以這里我們可以設(shè)計一個小小的算法來讓電腦變得聰明起來,讓它擁有攔截和判斷功能。具體思路和代碼如下:
(1)、判斷自己是否會贏(CheckComputer)
//電腦檢查自己是否會贏
//約定如果在函數(shù)內(nèi)部成功判斷就返回1
//判斷失敗則返回0
int CheckComputer(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判斷每一行是否有兩個相連的棋子,如果有,且第三個棋格為空,則落棋
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][0] == '#' && board[i][2] == ' ')
{
board[i][2] = '#';
return 1; //成功判斷,返回1
}
if (board[i][0] == board[i][2] && board[i][0] == '#' && board[i][1] == ' ')
{
board[i][1] = '#';
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == '#' && board[i][0] == ' ')
{
board[i][0] = '#';
return 1;
}
}
//判斷每一列是否有兩個相連的棋子,如果有,且第三個棋格為空,則落棋
for (j = 0; j < col; j++)
{
if (board[0][j] == board[1][j] && board[0][j] == '#' && board[2][j] == ' ')
{
board[2][j] = '#';
return 1;
}
if (board[0][j] == board[2][j] && board[0][j] == '#' && board[1][j] == ' ')
{
board[1][j] = '#';
return 1;
}if (board[1][j] == board[2][j] && board[1][j] == '#' && board[0][j] == ' ')
{
board[0][j] = '#';
return 1;
}
}
//判斷兩條對角線是否有兩個相連的棋子,如果有,且第三個棋格為空,則落棋
{
//第一條
if (board[0][0] == board[1][1] && board[0][0] == '#' && board[2][2] == ' ')
{
board[2][2] = '#';
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == '#' && board[1][1] == ' ')
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == '#' && board[0][0] == ' ')
{
board[0][0] = '#';
return 1;
}
//第二條
if (board[0][2] == board[1][1] && board[0][2] == '#' && board[2][0] == ' ')
{
board[2][0] = '#';
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == '#' && board[1][1] == ' ')
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == '#' && board[0][2] == ' ')
{
board[0][2] = '#';
return 1;
}
//如果上面都沒返回,說明不符合贏的條件,返回0
return 0;
}
}(2)、對玩家進行攔截(CheckPlayer)
//電腦檢查玩家是否會贏(邏輯和CheckComputer完全相同)
//約定成功攔截返回1
//無需攔截或者攔截不了返回0
int CheckPlayer(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判斷每一行是否有兩個相連的棋子,如果有,且第三個棋格為空,則攔截
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][0] == '*' && board[i][2] == ' ')
{
board[i][2] = '#';
return 1; //成功攔截,返回1
}
if (board[i][0] == board[i][2] && board[i][0] == '*' && board[i][1] == ' ')
{
board[i][1] = '#';
return 1;
}
if (board[i][1] == board[i][2] && board[i][1] == '*' && board[i][0] == ' ')
{
board[i][0] = '#';
return 1;
}
}
//判斷每一列是否有兩個相連的棋子,如果有,且第三個棋格為空,則攔截
for (j = 0; j < col; j++)
{
if (board[0][j] == board[1][j] && board[0][j] == '*' && board[2][j] == ' ')
{
board[2][j] = '#';
return 1;
}
if (board[0][j] == board[2][j] && board[0][j] == '*' && board[1][j] == ' ')
{
board[1][j] = '#';
return 1;
}if (board[1][j] == board[2][j] && board[1][j] == '*' && board[0][j] == ' ')
{
board[0][j] = '#';
return 1;
}
}
//判斷兩條對角線是否有兩個相連的棋子,如果有,且第三個棋格為空,則攔截
{
//第一條
if (board[0][0] == board[1][1] && board[0][0] == '*' && board[2][2] == ' ')
{
board[2][2] = '#';
return 1;
}
if (board[0][0] == board[2][2] && board[0][0] == '*' && board[1][1] == ' ')
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][2] && board[1][1] == '*' && board[0][0] == ' ')
{
board[0][0] = '#';
return 1;
}
//第二條
if (board[0][2] == board[1][1] && board[0][2] == '*' && board[2][0] == ' ')
{
board[2][0] = '#';
return 1;
}
if (board[0][2] == board[2][0] && board[0][2] == '*' && board[1][1] == ' ')
{
board[1][1] = '#';
return 1;
}
if (board[1][1] == board[2][0] && board[1][1] == '*' && board[0][2] == ' ')
{
board[0][2] = '#';
return 1;
}
//如果上面都沒返回,說明不符合攔截的條件,返回0
return 0;
}
}注意:我這里采用的判斷方法是枚舉,由于五子棋的枚舉情況比較復雜,而我目前也沒想到更好的算法來進行判斷,所以這里我只寫了三子棋的AI判斷代碼,如果有大佬有更好的算法或者判斷思路,歡迎在評論區(qū)留言。
(3)、加入AI算法后game.c的改動
上面我們已經(jīng)完成了CheckComputer和CheckPlayer這兩個函數(shù)的定義,現(xiàn)在我們只需要把這兩個函數(shù)實現(xiàn)放入到game.c中并且在在電腦下棋(ComputerMove)中調(diào)用這兩個函數(shù)即可。
//電腦下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("電腦下棋\n");
//定義兩個標識符變量來接收兩個判斷函數(shù)的返回值
int flag1 = 0;
int flag2 = 0;
flag1 = CheckComputer(board, row, col);
//如果flag1 == 0 時才進行flag2 的判斷,避免當二者都為1時下兩步棋(易錯)
if (flag1 == 0)
{
flag2 = CheckPlayer(board, row, col);
}
if (flag1 == 0 && flag2 == 0) //當CheckComputer和CheckPlayer都沒落棋時,就隨機下
{
while (1)
{
//在主函數(shù)生成種子srand
//隨機生成范圍內(nèi)的坐標
int x = rand() % row;
int y = rand() % col;
//判斷坐標是否被占用
if (board[x][y] == ' ')
{
board[x][y] = '#'; //假設(shè)電腦為#號
break;
}
}
}
}注意:這里的AI算法只適用于三子棋,如果要使用的話需要把頭文件中的ROW和COL改為3,同時不要忘記在頭文件中對兩個判斷函數(shù)進行聲明。
到此這篇關(guān)于C語言實現(xiàn)五子棋功能全解析的文章就介紹到這了,更多相關(guān)C語言五子棋內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C/C++可變參數(shù)函數(shù)的實現(xiàn)
這篇文章主要介紹了C/C++可變參數(shù)函數(shù)的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-04-04
舉例講解C語言的fork()函數(shù)創(chuàng)建子進程的用法
fork函數(shù)是Linux下一個近乎專有的C語言函數(shù),因為使用時需要調(diào)用unistd.h這個頭文件,這里我們就在Linux環(huán)境下舉例講解C語言的fork()函數(shù)創(chuàng)建子進程的用法,需要的朋友可以參考下2016-06-06

