這次寫五子棋程序還是緣於一個機遇(某男子學院狂轟濫炸式上課的C++老師佈置的作業)
然後我就開始用我蹩腳的C++語法知識,開始寫五子棋程序的框架。這一篇的只是會寫的比較基礎一些,首先可以大致分爲:模式選擇、打印棋盤、玩家下棋,電腦計算並下棋,判斷勝負。知識層的話可以分爲:
1、格式化輸出;
2、二維數組的訪問和操作;
3、預測棋盤局勢並判斷最優解輸出;
其實程序好像並不難寫,並沒有用到什麼很難的語法或者是高級技巧,主要是一開始寫的時候框架要清晰,一邊寫一邊調試確保基本的函數沒有錯誤,保證後面的調用也不會出錯。剩下的就是對棋盤各種情況的識別和判斷權重(其實就是瘋狂碼就對了)。
格式化輸出
其實這算是很基礎的知識了,這裏特地要說一下主要是我的問題。。。每次要涉及對字符串的處理的時候總能寫出一堆bug,一開始是想要用字符串數組寫的?後來不行想帶遍歷二維整數數組,然後根據不同數字輸出,這裏就簡單提一下:
先對二維數組全部賦值爲0,當遍歷到0時輸出" (空) “,獲取玩家輸入的座標時對應二維數組的數字賦值爲1,遍歷到1時輸出” o “,機器判斷輸出最優解時對應數字賦值爲2,遍歷到2時輸出爲"●”。
//打印棋盤函數
void print_chessboard()
{
printf(" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\n");
for (int i = 0; i < 15; i++)
{
printf(" |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n");
printf("%3d ", i + 1);
for (int j = 0; j < 15; j++)
{
printf("|");
if (chess_board[i][j] == 0)
{
printf("%3s", " ");
}
else if (chess_board[i][j] == 1)
{
printf("%3s", "○");
}
else if (chess_board[i][j] == 2)
{
printf("%3s", "●");
}
}
printf("|\n");
}
printf(" |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n");
}
想要居中輸出的話,可以用cout和setw(),計算居中的位置做setw的參數就好啦。
打印出的棋盤效果是這樣的:
判斷勝負機制:
要達到勝利的情況其實只有四種情況:1、五子橫(五個棋子橫向相連);2、五子豎(五個棋子縱向相連);3、五子撇(五個棋子向左下斜);4、五子捺(五個棋子向右下斜);
所以只需要對二維數組進行遍歷,對每個遍歷點都進行這四個方向的檢測就可以實現判斷棋盤上是否產生贏家。
//判斷雙方勝負機制
int judgement(int player,int flag)
{
/******************************************
共有四種五子連線的情況,所以分爲五種勝利條件
1:五子橫 2:五子豎 3:五子撇 4:五子捺
********************************************/
//判斷人類玩家是否勝利
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < 15; j++)
{
if (chess_board[i][j] == flag)
{
//判斷五子橫是否成立
if (chess_board[i][j + 1] == flag)
{
if (chess_board[i][j + 2] == flag)
{
if (chess_board[i][j + 3] == flag)
{
if (chess_board[i][j + 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
//判斷五子豎是否成立
if (chess_board[i+1][j] == flag)
{
if (chess_board[i+2][j] == flag)
{
if (chess_board[i+3][j] == flag)
{
if (chess_board[i+4][j] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
//判斷五子撇是否成立
if (j>=5&&i<=10)
{
if (chess_board[i + 1][j - 1] == flag)
{
if (chess_board[i + 2][j - 2] == flag)
{
if (chess_board[i + 3][j - 3] == flag)
{
if (chess_board[i + 4][j - 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
}
//判斷五子捺是否成立
if (i <= 10 && j <= 10)
{
if (chess_board[i + 1][j + 1] == flag)
{
if (chess_board[i + 2][j + 2] == flag)
{
if (chess_board[i + 3][j + 3] == flag)
{
if (chess_board[i + 4][j + 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
}
}
}
}
}
定義玩家結構體
//定義玩家結構體
typedef struct players
{
int x=0, y=0; //玩家本次下棋的座標
int precious_x=0, precious_y=0; //玩家上一次下棋的座標
}Player;
主要是爲了記錄本次要下棋的座標和上一次的座標,便於提供給電腦進行計算和檢測座標的合法性。(後面會講到)
計算最優解
這部分可以說最難但其實難不是難在技術,難在代碼多······研究了五子棋的各種情況就可以開始拼命碼了。
大體的話可以分爲六個優先級
1、第一優先級:當電腦檢測到己方棋子存在四個相連的情況時,第一優先響應練成五個獲得勝利
2、第二優先級:當檢測到玩家有四個相連時,對四個棋子的兩邊進行圍堵,網上看到有人分爲什麼活四啊眠四啊,我覺得這裏好像沒什麼吧必要,對四個的進行檢測然後往兩邊空的堵上就好了,要是活四的話堵不堵反正也輸了,而且只要優先級設置的正確的話,基本上是不會出現活四的情況的
3、第三優先級:當檢測到己方有三個相連且兩邊爲空時,連成四個,懂得遊戲規則的都知道,這樣的話下一步就贏了。
4、第四優先級:當檢測到玩家有三個相連且兩邊爲空時,對其兩邊進行圍堵,防止其出現活四,當然要是不是爲空的話可堵可不堵,要是這方面也要判斷的話代碼就寫不完了hhh,所以這裏看你的下棋風格吧
5、第五優先級:當檢測到己方有兩個相連且兩邊爲空時,連成三個爲下一次做準備
6、第六優先級:當檢測到玩家有兩個相連且兩邊爲空時,進行圍堵,以防後患(當然這些都是建立在前面不成立的前提)
如果上述都不存在的話怎麼辦呢?
爲了不要讓電腦隨便亂走浪費次數,我們可以對前面記錄的玩家下棋的座標
採樣rand()讓電腦在無風險時下在玩家下棋的周圍,阻止玩家任意佈局。這裏沒辦法列出所有程序。因爲有點多,就只寫出最基本的隨機預測:
int rand_num1 = (rand() % 3) - 1; //rand_num1介於[-1,1]
int rand_num2 = (rand() % 3) - 1; //rand_num2介於[-1,1]
if (chess_board[human.x + rand_num1][human.y + rand_num2] == 0)
{
computer.x = human.x + rand_num1;
computer.y = human.y + rand_num2;
return 1;
}
檢測座標合法性
這一步其實也很重要,不寫的話後面會有很多bug,而且後面很多要用到。
//判斷玩家和電腦落子座標是否合法
bool isture(int x,int y)
{
if (x < 0 | x >= 15 | y < 0 | y >= 15)
{
cout << "輸入座標位於棋盤外" << endl;
return false;
}
else if (chess_board[x][y] == 1 | chess_board[x][y] == 2)
{
cout << "輸入座標已有棋子" << endl;
return false;
}
else return true;
}
詳細的源碼我放在這裏啦,有需要可以下載 智能五子棋程序
不吹不黑,要下贏它還是需要斟酌的,之後我還會慢慢改進,有膽量的話就去跟他較量較量吧!
嗚嗚嗚,可能是我太菜了,自己寫的程序自己還贏不過