基于C的α-β剪枝算法实现的AI五子棋游戏
對(duì)抗問(wèn)題
對(duì)抗問(wèn)題:顧名思義,博弈雙方是帶有對(duì)抗性質(zhì)的。博弈的任何一方都希望局面盡量對(duì)自己有利,同時(shí)局面也應(yīng)該盡量令對(duì)方不利。通常這一類問(wèn)題可以通過(guò) Minimax 算法解決。
Minimax 算法又名極小化極大算法,是一種找出失敗的最大可能性中的最小值的算法。
Minimax 算法常用于棋類等由兩方較量的游戲和程序,這類程序由兩個(gè)游戲者輪流,每次執(zhí)行一個(gè)步驟。
為了執(zhí)行Minimax 算法,我們可以通過(guò)窮舉的方式,枚舉所有的狀態(tài)空間,從而使得我們可以在游戲剛一開(kāi)始,就預(yù)測(cè)到輸贏。
但是,在實(shí)際情況下,游戲的狀態(tài)空間都是異常龐大的。很顯然,我們不能將以窮舉方式實(shí) 現(xiàn)的Minimax 算法用于實(shí)際應(yīng)用。
α-β 減枝
通過(guò)分析可以發(fā)現(xiàn),在利用窮舉方法執(zhí)行 Minimax 算法中有許多的無(wú)效搜索,也就是說(shuō),許多明顯較劣的狀態(tài)分支我們也進(jìn)行搜索了。
我們?cè)谶M(jìn)行極大值搜索的時(shí)候,我們僅僅關(guān)心,下面最大的狀態(tài),對(duì)于任何小于目前值 的分支也都是完全沒(méi)有必要進(jìn)行進(jìn)一步檢查的。(α 減枝)
通過(guò)上圖,我們可以發(fā)現(xiàn),我們可以減去大量無(wú)用的狀態(tài)檢查,從而降低我們的運(yùn)算量。
同時(shí),我們?cè)谶M(jìn)行極小值搜索的時(shí)候,我們僅僅關(guān)心,下面最小的狀態(tài),對(duì)于任何大于 目前值的分支都是完全沒(méi)有必要進(jìn)行進(jìn)一步檢查的。(β 減枝)
通過(guò)上圖,我們可以發(fā)現(xiàn),我們可以減去大量無(wú)用的狀態(tài)檢查,從而降低我們的運(yùn)算量。 將上述所提到的 α 減枝與 β 減枝進(jìn)行綜合就可以得到 α-β 減枝。
對(duì)于對(duì)抗搜索而言,我們需要精心設(shè)計(jì)其估值函數(shù),不然我們的 α-β 減枝將毫無(wú)用武之地。
五子棋問(wèn)題
五子棋:**是一種兩人對(duì)弈的純策略型棋類游戲,通常雙方分別使用黑白兩色的棋子,下在棋 盤(pán)直線與橫線的交叉點(diǎn)上,先形成 5 子連線者獲勝。
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-9KrUHl7H-1617337672265)(media/4e9ed78b0312128f30ed0dde6f043b13.jpeg)]
這里,我們采用了極大極小博弈樹(shù)(MGT),來(lái)實(shí)現(xiàn)AI
這里用一張井字棋的搜索示意圖來(lái)說(shuō)明。
上圖很清晰的展示了對(duì)局可能出現(xiàn)的所有情況(已經(jīng)去除了等價(jià)的情況),如果讓這個(gè)圖延展下去,我們就相當(dāng)于窮舉了所有的下法,如果我們能在知道所有下法的情況下,對(duì)這 些下法加以判斷,我們的AI 自然就可以選擇具有最高獲勝可能的位置來(lái)下棋。
極大極小博弈樹(shù)就是一種選擇方法,由于五子棋以及大多數(shù)博弈類游戲是無(wú)法窮舉出所有可能的步驟的(狀態(tài)會(huì)隨著博弈樹(shù)的擴(kuò)展而呈指數(shù)級(jí)增長(zhǎng)),所以通常我們只會(huì)擴(kuò)展有限的層數(shù),而 AI 的智能高低,通常就會(huì)取決于能夠擴(kuò)展的層數(shù),層數(shù)越高,AI 了解的信息就越多, 就越能做出有利于它的判斷。
為了讓計(jì)算機(jī)選擇那些獲勝可能性高的步驟走,我們就需要一個(gè)對(duì)局面進(jìn)行打分的算法, 越有利,算法給出的分?jǐn)?shù)越高。在得到這個(gè)算法過(guò)后,計(jì)算機(jī)就可以進(jìn)行選擇了,在極大極小博弈樹(shù)上的選擇規(guī)則是這樣的:
AI 會(huì)選擇子樹(shù)中具有最高估值葉子節(jié)點(diǎn)的路徑;
USER 會(huì)選擇子樹(shù)中具有最小估值葉子節(jié)點(diǎn)的路徑。
這樣的原則很容易理解,作為玩家,我所選擇的子一定要使自己的利益最大化,而相應(yīng) 的在考慮對(duì)手的時(shí)候,也不要低估他,一定要假設(shè)他會(huì)走對(duì)他自己最有利,也就是對(duì)我最不 利的那一步。
接下來(lái),我們實(shí)現(xiàn)關(guān)鍵的局面評(píng)分步驟:
直接分析整個(gè)棋面是一件很復(fù)雜的事情,為了讓其具備可分析性,我們可以將其進(jìn)行分 解,分解成易于我們理解和實(shí)現(xiàn)的子問(wèn)題。
對(duì)于一個(gè)二維的期面,五子棋不同于圍棋,五子棋的勝負(fù)只取決于一條線上的棋子,所 以根據(jù)五子棋的這一特征,我們就來(lái)考慮將二維的棋面轉(zhuǎn)換為一維的,下面是一種簡(jiǎn)單的思 考方式,對(duì)于整個(gè)棋盤(pán),我們只需要考慮四個(gè)方向即可,所以我們就按照四個(gè)方向來(lái)將棋盤(pán) 轉(zhuǎn)換為 15 * 6 個(gè)長(zhǎng)度不超過(guò) 15 的一維向量(分解斜向的時(shí)候,需要分為上下兩個(gè)半?yún)^(qū)),參考下圖:
我們的目的是為了為其評(píng)分,那么我們就還需要評(píng)估每個(gè)線狀態(tài),將每個(gè)線狀態(tài)的評(píng)分進(jìn)行匯總,當(dāng)做我們的棋面評(píng)分:
接下來(lái)我們所要做的就是評(píng)價(jià)每一條線狀態(tài),根據(jù)五子棋的規(guī)則,我們可以很容易窮舉出各種可能出現(xiàn)的基本棋型,我們首先為這些基本棋型進(jìn)行識(shí)別和評(píng)價(jià), 并且統(tǒng)計(jì)每個(gè)線狀態(tài)中出現(xiàn)了多少種下面所述的棋型,并據(jù)此得出評(píng)價(jià)值,得到如下圖所示的靜態(tài)估值表:
根據(jù)這個(gè)表以及我們之前所談到的規(guī)則,我們就可以得到一個(gè)可以運(yùn)行的AI 了。
進(jìn)一步的優(yōu)化
注意到,如果我們搜索到第四層,總共需要搜索:
224 + 224 * 223 + 224 * 223 * 222 + 224 * 223 * 222 * 221 = 2 461 884 544 個(gè) 狀
態(tài)節(jié)點(diǎn),搜索如此多的狀態(tài)節(jié)點(diǎn)的開(kāi)銷是十分可觀的,因此,我們提高效率的方式就鎖定到了:如何減少需要搜索的狀態(tài)節(jié)點(diǎn)。
我們可以采取以下方法來(lái)減少需要搜索的狀態(tài)節(jié)點(diǎn):
我們可以利用經(jīng)典的α-β剪枝算法對(duì)博弈樹(shù)剪枝。
我們可以每次搜索僅搜索落子點(diǎn)周圍 2*2 格范圍內(nèi)存在棋子的位置,這樣可以避免搜索一些明顯無(wú)用的節(jié)點(diǎn),而且可以大幅度提升整體搜索速度。
避免對(duì)必勝/負(fù)局面搜索,當(dāng)搜索過(guò)程中出現(xiàn)了必勝/負(fù)局面的時(shí)候直接返回不再搜索, 因?yàn)榇藭r(shí)繼續(xù)搜索是沒(méi)有必要的,直接返回當(dāng)前棋局的估價(jià)值即可。
加入隨機(jī)化AI 的下棋方式,普通的AI 算法對(duì)于給定的玩家下棋方式會(huì)給出固定的回應(yīng), 這就導(dǎo)致玩家獲勝一次之后只要此后每次都按此方式下棋,都能夠獲勝。為了避免這種 情況,可以在 AI 選擇下子位置的時(shí)候,在估值相差不多的幾個(gè)位置中隨機(jī)挑選一個(gè)進(jìn)行放置,以此增加AI 的靈活性。
規(guī)劃搜索順序,有很多有價(jià)值的下子點(diǎn)存在于更靠近棋盤(pán)中央的地方,如果從棋盤(pán)中央 向外搜索的話,則能夠提高α-β剪枝的效率,讓盡可能多的分支被排除。
實(shí)驗(yàn)成果
(游戲中間界面)
(游戲中間界面)
實(shí)驗(yàn)總結(jié)
通過(guò)本次實(shí)驗(yàn),加強(qiáng)了組員之間的溝通協(xié)調(diào)能力,同時(shí)也提高了我們對(duì)αβ減枝算法的 了解。我們更了解了五子棋相關(guān)的游戲規(guī)則以及一些技巧,拓寬了我們的知識(shí)面,有助于我 們?cè)谖磥?lái)的生活中更好的與人交流。
同時(shí),經(jīng)過(guò)此次實(shí)驗(yàn),我們深入了解了棋類人工智能算法,進(jìn)一步的提升了我們的專業(yè) 水平,有助于我們?cè)谖磥?lái)的就業(yè)與科研崗位上走的更遠(yuǎn)。
雖然αβ減枝實(shí)現(xiàn)起來(lái)非常容易,但是五子棋的局勢(shì)估計(jì)卻十分的有挑戰(zhàn)性,其局勢(shì)估 計(jì)的準(zhǔn)確與否直接影響了程序的運(yùn)行結(jié)果是否令人滿意,AI 是否能夠展現(xiàn)出足夠的智能。
本次實(shí)驗(yàn),由周宇星(1352652)和蔡樂(lè)文(1353100)完成。其中,周宇星設(shè)計(jì)并完成 了五子棋布局的估價(jià)函數(shù),與蔡樂(lè)文共同完成了αβ減枝算法的研究與實(shí)現(xiàn)。蔡樂(lè)文設(shè)計(jì)并 完成了有關(guān)程序界面以及操作的功能。
源代碼
#include “opencv2/imgproc/imgproc.hpp” #include “opencv2/imgcodecs.hpp”
#include “opencv2/videoio/videoio.hpp” #include “opencv2/highgui/highgui.hpp”
#include <iostream>
#include <iostream> #include <cstdio> #include <cstring> #include
<cstdio> #include <cstdlib> #include <iomanip>
using namespace std; using namespace cv;
//sro 菜神 Orz
cv::Mat chessboard, whiteChess, blackChess, tmp, BGS;
int is_red(Vec3b X) {
// cout << (int)X[1] << ’ ’ << (int)X[2] << ’ ’ << (int)X[3] <<
endl;
return X[0] < 200 && X[1] < 200 && X[2] > 230;
}
cv::Mat BG;
//將棋子復(fù)制到背景畫(huà)面上
void imageCopyToBG(cv::Mat chess, int x, int y) {
x *= 35;
y *= 35;
int rows = chess.rows;
int cols = chess.cols;
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (!is_red(chess.at<Vec3b>(i, j))) {
BG.at<Vec3b>(x + i + 8, y + j + 8) = chess.at<Vec3b>(i, j);
}
}
}
}
/*
實(shí)現(xiàn)用的參數(shù)
*/
class CONFIG { public:
static const int BOARD_SIZE = 15;
static const int EMPTY = 0;
static const int USER_1 = 1;
static const int USER_2 = 2;
static const int AI_EMPTY = 0; // 無(wú)子
static const int AI_MY = 1; // 待評(píng)價(jià)子
static const int AI_OP = 2; // 對(duì)方子或不能下子
static const int MAX_NODE = 2;
static const int MIN_NODE = 1;
static const int INF = 106666666;
static const int ERROR_INDEX = -1;
//估價(jià)值
static const int AI_ZERO = 0;
static const int AI_ONE = 10;
static const int AI_ONE_S = 1;
static const int AI_TWO = 100;
static const int AI_TWO_S = 10;
static const int AI_THREE = 1000;
static const int AI_THREE_S = 100;
static const int AI_FOUR = 10000;
static const int AI_FOUR_S = 1000;
static const int AI_FIVE = 100000;
};
/*
棋盤(pán)格子
*/
class Grid :CONFIG { public:
int type; //類型
Grid() {
type = EMPTY;
}
Grid(int t) {
type = t;
}
void grid(int t = EMPTY) {
type = t;
}
int isEmpty() {
return type == EMPTY ? true : false;
}
};
/* 棋盤(pán)
*/
class ChessBoard :CONFIG { public:
Grid chessBoard[BOARD_SIZE][BOARD_SIZE];
ChessBoard() {
for (int i = 0; i < BOARD_SIZE; ++i)
for (int j = 0; j < BOARD_SIZE; ++j)
chessBoard[i][j].grid();
}
ChessBoard(const ChessBoard &othr) {
for (int i = 0; i < BOARD_SIZE; ++i)
for (int j = 0; j < BOARD_SIZE; ++j)
chessBoard[i][j].grid(othr.chessBoard[i][j].type);
}
/*
放置棋子
返回放置棋子是否成功
*/
bool placePiece(int x, int y, int type) {
if (chessBoard[x][y].isEmpty()) {
chessBoard[x][y].type = type;
return true;
}
return false;
}
};
/*
煞筆AI
*/
class Game :CONFIG { public:
ChessBoard curState; // 當(dāng)前棋盤(pán)
bool isStart; // 是否進(jìn)行中
int curUser; // 當(dāng)前行棋人
int MAX_DEPTH; // 最大搜索層數(shù)
/*
開(kāi)始并設(shè)定難度
*/
void startGame(int nd = 2) {
MAX_DEPTH = nd;
isStart = true;
curUser = USER_1;
}
/*
轉(zhuǎn)換行棋人
*/
void changeUser() {
curUser = curUser == USER_1 ? USER_2 : USER_1;
}
/*
根據(jù)給定type
A:待判斷棋子的類型
type:我方棋子的類型
返回A是待判斷棋子 無(wú)棋子 對(duì)方棋子
*/
int getPieceType(int A, int type) {
return A == type ? AI_MY : (A == EMPTY ? AI_EMPTY : AI_OP);
}
int getPieceType(const ChessBoard &board, int x, int y, int type) {
if (x < 0 || y < 0 || x >= BOARD_SIZE || y >= BOARD_SIZE)//
超出邊界按對(duì)方棋子算
return AI_OP;
else
return getPieceType(board.chessBoard[x][y].type, type);
}
/*
當(dāng)前行棋人放置棋子
放置失敗返回失敗
放置成功
檢察游戲是否結(jié)束
轉(zhuǎn)換游戲角色后返回成功
*/
bool placePiece(int x, int y) {
if (curState.placePiece(x, y, curUser)) {
// 檢察行棋人是否勝利
if (isWin(x, y)) {
isStart = false; // 游戲結(jié)束
// return true;
}
changeUser(); // 轉(zhuǎn)換游戲角色
return true;
}
return false;
}
bool isWin(int x, int y) {
if (evaluatePiece(curState, x, y, curUser) >= AI_FIVE)
return true;
return false;
}
/*
以center作為評(píng)估位置進(jìn)行評(píng)價(jià)一個(gè)方向的棋子
*/
int evaluateLine(int line[], bool ALL) {
int value = 0; // 估值
int cnt = 0; // 連子數(shù)
int blk = 0; // 封閉數(shù)
for (int i = 0; i < BOARD_SIZE; ++i) {
if (line[i] == AI_MY) { // 找到第一個(gè)己方的棋子
// 還原計(jì)數(shù)
cnt = 1;
blk = 0;
// 看左側(cè)是否封閉
if (line[i - 1] == AI_OP)
++blk;
// 計(jì)算連子數(shù)
for (i = i + 1; i < BOARD_SIZE && line[i] == AI_MY; ++i, ++cnt);
// 看右側(cè)是否封閉
if (line[i] == AI_OP)
++blk;
// 計(jì)算評(píng)估值
value += getValue(cnt, blk);
}
}
return value;
}
/*
以center作為評(píng)估位置進(jìn)行評(píng)價(jià)一個(gè)方向的棋子(前后4格范圍內(nèi))
*/
int evaluateLine(int line[]) {
int cnt = 1; // 連子數(shù)
int blk = 0; // 封閉數(shù)
// 向左右掃
for (int i = 3; i >= 0; --i) {
if (line[i] == AI_MY) ++cnt;
else if (line[i] == AI_OP) {
++blk;
break;
}
else
break;
}
for (int i = 5; i < 9; ++i) {
if (line[i] == AI_MY) ++cnt;
else if (line[i] == AI_OP) {
++blk;
break;
}
else
break;
}
return getValue(cnt, blk);
}
/*
根據(jù)連字?jǐn)?shù)和封堵數(shù)給出一個(gè)評(píng)價(jià)值
*/
int getValue(int cnt, int blk) {
if (blk == 0) {// 活棋
switch (cnt) {
case 1:
return AI_ONE;
case 2:
return AI_TWO;
case 3:
return AI_THREE;
case 4:
return AI_FOUR;
default:
return AI_FIVE;
}
}
else if (blk == 1) {// 單向封死
switch (cnt) {
case 1:
return AI_ONE_S;
case 2:
return AI_TWO_S;
case 3:
return AI_THREE_S;
case 4:
return AI_FOUR_S;
default:
return AI_FIVE;
}
}
else {// 雙向堵死
if (cnt >= 5)
return AI_FIVE;
else
return AI_ZERO;
}
}
/*
對(duì)一個(gè)狀態(tài)的一個(gè)位置放置一種類型的棋子的優(yōu)劣進(jìn)行估價(jià)
*/
int evaluatePiece(ChessBoard state, int x, int y, int type) {
int value = 0; // 估價(jià)值
int line[17]; //線狀態(tài)
bool flagX[8];// 橫向邊界標(biāo)志
flagX[0] = x - 4 < 0;
flagX[1] = x - 3 < 0;
flagX[2] = x - 2 < 0;
flagX[3] = x - 1 < 0;
flagX[4] = x + 1 > 14;
flagX[5] = x + 2 > 14;
flagX[6] = x + 3 > 14;
flagX[7] = x + 4 > 14;
bool flagY[8];// 縱向邊界標(biāo)志
flagY[0] = y - 4 < 0;
flagY[1] = y - 3 < 0;
flagY[2] = y - 2 < 0;
flagY[3] = y - 1 < 0;
flagY[4] = y + 1 > 14;
flagY[5] = y + 2 > 14;
flagY[6] = y + 3 > 14;
flagY[7] = y + 4 > 14;
line[4] = AI_MY; // 中心棋子
// 橫
line[0] = flagX[0] ? AI_OP : (getPieceType(state.chessBoard[x - 4][y].type,
type));
line[1] = flagX[1] ? AI_OP : (getPieceType(state.chessBoard[x - 3][y].type,
type));
line[2] = flagX[2] ? AI_OP : (getPieceType(state.chessBoard[x - 2][y].type,
type));
line[3] = flagX[3] ? AI_OP : (getPieceType(state.chessBoard[x - 1][y].type,
type));
line[5] = flagX[4] ? AI_OP : (getPieceType(state.chessBoard[x + 1][y].type,
type));
line[6] = flagX[5] ? AI_OP : (getPieceType(state.chessBoard[x + 2][y].type,
type));
line[7] = flagX[6] ? AI_OP : (getPieceType(state.chessBoard[x + 3][y].type,
type));
line[8] = flagX[7] ? AI_OP : (getPieceType(state.chessBoard[x + 4][y].type,
type));
value += evaluateLine(line);
// 縱
line[0] = flagY[0] ? AI_OP : getPieceType(state.chessBoard[x][y - 4].type,
type);
line[1] = flagY[1] ? AI_OP : getPieceType(state.chessBoard[x][y - 3].type,
type);
line[2] = flagY[2] ? AI_OP : getPieceType(state.chessBoard[x][y - 2].type,
type);
line[3] = flagY[3] ? AI_OP : getPieceType(state.chessBoard[x][y - 1].type,
type);
line[5] = flagY[4] ? AI_OP : getPieceType(state.chessBoard[x][y + 1].type,
type);
line[6] = flagY[5] ? AI_OP : getPieceType(state.chessBoard[x][y + 2].type,
type);
line[7] = flagY[6] ? AI_OP : getPieceType(state.chessBoard[x][y + 3].type,
type);
line[8] = flagY[7] ? AI_OP : getPieceType(state.chessBoard[x][y + 4].type,
type);
value += evaluateLine(line);
// 左上-右下
line[0] = flagX[0] || flagY[0] ? AI_OP : getPieceType(state.chessBoard[x -
4][y
- 4].type, type);
line[1] = flagX[1] || flagY[1] ? AI_OP : getPieceType(state.chessBoard[x
- 3][y
- 3].type, type);
line[2] = flagX[2] || flagY[2] ? AI_OP : getPieceType(state.chessBoard[x -
2][y
- 2].type, type);
line[3] = flagX[3] || flagY[3] ? AI_OP : getPieceType(state.chessBoard[x -
1][y
- 1].type, type);
line[5] = flagX[4] || flagY[4] ? AI_OP : getPieceType(state.chessBoard[x +
1][y
+ 1].type, type);
line[6] = flagX[5] || flagY[5] ? AI_OP : getPieceType(state.chessBoard[x +
2][y
+ 2].type, type);
line[7] = flagX[6] || flagY[6] ? AI_OP : getPieceType(state.chessBoard[x +
3][y
+ 3].type, type);
line[8] = flagX[7] || flagY[7] ? AI_OP : getPieceType(state.chessBoard[x +
4][y
+ 4].type, type);
value += evaluateLine(line);
// 右上-左下
line[0] = flagX[7] || flagY[0] ? AI_OP : getPieceType(state.chessBoard[x +
4][y
- 4].type, type);
line[1] = flagX[6] || flagY[1] ? AI_OP : getPieceType(state.chessBoard[x +
3][y
- 3].type, type);
line[2] = flagX[5] || flagY[2] ? AI_OP : getPieceType(state.chessBoard[x +
2][y
- 2].type, type);
line[3] = flagX[4] || flagY[3] ? AI_OP : getPieceType(state.chessBoard[x +
1][y
- 1].type, type);
line[5] = flagX[3] || flagY[4] ? AI_OP : getPieceType(state.chessBoard[x -
1][y
+ 1].type, type);
line[6] = flagX[2] || flagY[5] ? AI_OP : getPieceType(state.chessBoard[x -
2][y
+ 2].type, type);
line[7] = flagX[1] || flagY[6] ? AI_OP : getPieceType(state.chessBoard[x -
3][y
+ 3].type, type);
line[8] = flagX[0] || flagY[7] ? AI_OP : getPieceType(state.chessBoard[x -
4][y
+ 4].type, type);
value += evaluateLine(line);
return value;
}
/*
評(píng)價(jià)一個(gè)棋面上的一方
*/
int evaluateState(ChessBoard state, int type) {
int value = 0;
// 分解成線狀態(tài)
int line[6][17];
int lineP;
for (int p = 0; p < 6; ++p)
line[p][0] = line[p][16] = AI_OP;
// 從四個(gè)方向產(chǎn)生
for (int i = 0; i < BOARD_SIZE; ++i) {
// 產(chǎn)生線狀態(tài)
lineP = 1;
for (int j = 0; j < BOARD_SIZE; ++j) {
line[0][lineP] = getPieceType(state, i, j, type); /* | */
line[1][lineP] = getPieceType(state, j, i, type); /* - */
line[2][lineP] = getPieceType(state, i + j, j, type); /* \ */
line[3][lineP] = getPieceType(state, i - j, j, type); /* / */
line[4][lineP] = getPieceType(state, j, i + j, type); /* \ */
line[5][lineP] = getPieceType(state, BOARD_SIZE - j - 1, i + j, type);
/* / */
++lineP;
}
// 估計(jì)
int special = i == 0 ? 4 : 6;
for (int p = 0; p < special; ++p) {
value += evaluateLine(line[p], true);
}
}
return value;
}
/*
若x, y位置周圍1格內(nèi)有棋子則搜索
*/
bool canSearch(ChessBoard state, int x, int y) {
int tmpx = x - 1;
int tmpy = y - 1;
for (int i = 0; tmpx < BOARD_SIZE && i < 3; ++tmpx, ++i) {
int ty = tmpy;
for (int j = 0; ty < BOARD_SIZE && j < 3; ++ty, ++j) {
if (tmpx >= 0 && ty >= 0 && state.chessBoard[tmpx][ty].type != EMPTY)
return true;
else
continue;
}
}
return false;
}
/*
給出后繼節(jié)點(diǎn)的類型
*/
int nextType(int type) {
return type == MAX_NODE ? MIN_NODE : MAX_NODE;
}
/*
state 待轉(zhuǎn)換的狀態(tài)
type 當(dāng)前層的標(biāo)記:MAX MIN
depth 當(dāng)前層深
alpha 父層alpha值
beta 父層beta值
*/
int minMax(ChessBoard state, int x, int y, int type, int depth, int alpha, int
beta) {
ChessBoard newState(state);
newState.placePiece(x, y, nextType(type));
int weight = 0;
int max = -INF; // 下層權(quán)值上界
int min = INF; // 下層權(quán)值下界
if (depth < MAX_DEPTH) {
// 已輸或已勝則不繼續(xù)搜索
if (evaluatePiece(newState, x, y, nextType(type)) >= AI_FIVE) {
if (type == MIN_NODE)
return AI_FIVE; // 我方勝
else
return -AI_FIVE;
}
int i, j;
for (i = 0; i < BOARD_SIZE; ++i) {
for (j = 0; j < BOARD_SIZE; ++j) {
if (newState.chessBoard[i][j].type == EMPTY && canSearch(newState, i, j)) {
weight = minMax(newState, i, j, nextType(type), depth + 1, min, max);
if (weight > max)
max = weight; // 更新下層上界
if (weight < min)
min = weight; // 更新下層下界
// alpha-beta
if (type == MAX_NODE) {
if (max >= alpha)
return max;
}
else {
if (min <= beta)
return min;
}
}
else
continue;
}
}
if (type == MAX_NODE)
return max; // 最大層給出最大值
else
return min; // 最小層給出最小值
}
else {
weight = evaluateState(newState, MAX_NODE); // 評(píng)估我方局面
weight -= type == MIN_NODE ? evaluateState(newState, MIN_NODE) * 10 :
evaluateState(newState, MIN_NODE); // 評(píng)估對(duì)方局面
return weight; // 搜索到限定層后給出權(quán)值
}
}
int cnt[BOARD_SIZE][BOARD_SIZE];
/*
AI 行棋
*/
bool placePieceAI() {
int weight;
int max = -INF; // 本層的權(quán)值上界
int x = 0, y = 0;
memset(cnt, 0, sizeof(cnt));
for (int i = 0; i < BOARD_SIZE; ++i) {
for (int j = 0; j < BOARD_SIZE; ++j) {
if (curState.chessBoard[i][j].type == EMPTY && canSearch(curState, i, j)) {
weight = minMax(curState, i, j, nextType(MAX_NODE), 1, -INF, max);
cnt[i][j] = weight;
if (weight > max) {
max = weight; // 更新下層上界
x = i;
y = j;
}
}
else
continue;
}
}
return placePiece(x, y); // AI最優(yōu)點(diǎn)
}
/*
控制臺(tái)打印。。。
*/
void show() {
chessboard.copyTo(BG);
for (int i = 0; i < BOARD_SIZE; ++i) {
for (int j = 0; j < BOARD_SIZE; ++j) {
if (curState.chessBoard[i][j].type == 1)
imageCopyToBG(blackChess, i, j);
if (curState.chessBoard[i][j].type == 2)
imageCopyToBG(whiteChess, i, j);
}
}
for (int i = 0; i < BOARD_SIZE; ++i) {
for (int j = 0; j < BOARD_SIZE; ++j) {
if (curState.chessBoard[i][j].type == 0)
cout << " -";
if (curState.chessBoard[i][j].type == 1)
cout << " X";
if (curState.chessBoard[i][j].type == 2)
cout << " O";
}
cout << endl;
}
imshow(“gobang”, BG);
cv::waitKey(5);
}
};
using namespace cv; using namespace std;
int X, Y = 0; int optIsOk = 1; Game G;
//使用回調(diào)函數(shù)輸入數(shù)據(jù)
static void onMouse(int event, int x, int y, int, void*)
{
if (event != EVENT_LBUTTONDOWN)
return;
if (!optIsOk) return;
X = x; Y = y;
optIsOk = 0;
}
int main(int argc, char** argv)
{
chessboard = cv::imread(“chessboard.bmp”);
tmp = cv::imread(“whiteChess.bmp”);
resize(tmp, whiteChess, Size(30, 30), 0, 0, CV_INTER_LINEAR);
tmp = cv::imread(“blackChess.bmp”);
resize(tmp, blackChess, Size(30, 30), 0, 0, CV_INTER_LINEAR);
namedWindow(“gobang”, 1);
setMouseCallback(“gobang”, onMouse, 0);
chessboard.copyTo(BG);
imshow(“gobang”, BG);
cv::waitKey(50);
int flag = 0;
G.startGame(4);
for (;😉
{
G.placePieceAI();
G.show();
G.placePieceAI();
G.show();
optIsOk = 1;
// }
cv::waitKey(5);
}
return 0;
}
總結(jié)
以上是生活随笔為你收集整理的基于C的α-β剪枝算法实现的AI五子棋游戏的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 厦门大学计算机研究生2020专业目录,报
- 下一篇: AI 趋势