象棋人工智慧演算法的C++實現(四)——人工智慧的開端
前言:前面幾篇部落格詳細介紹了棋盤類的封裝、棋子類的封裝以及各種型別的棋子的走棋演算法的實現。有了前面的鋪墊,就能邁出人工智慧的第一步了。本系列部落格還是重點介紹實現方法,很多的程式碼都不再過多解釋了。
人機對戰類:
#ifndef SINGLEGAME_H #define SINGLEGAME_H #include "Board.h" class SingleGame : public Board { public: virtual void click(int id,int row,int col); //獲取所有走棋路徑存放到steps中 void getAllPossibleMove(QVector<Step *>& steps); //評估局面分 int calcScore(); //假裝走一步 void fakeMove(Step* step); //將假裝走的那一步挪回來 void unfakeMove(Step* step); //獲取最佳走棋路徑 Step* getBestMove(); //電腦走棋 void computerMove(); }; #endif // SINGLEGAME_H
由以上程式碼可以看出,人機對戰類繼承自棋盤類,過載了棋盤類中的click函式。其中,Step是一個QVector容器,其內部情況是這樣的:
#ifndef STEP_H #define STEP_H #include <QObject> class Step : public QObject { Q_OBJECT public: explicit Step(QObject *parent = 0); ~Step(); //行走棋子的id int _moveid; //殺掉棋子的id int _killid; //起始行座標 int _rowFrom; //起始列座標 int _colFrom; //目標位置行座標 int _rowTo; //目標位置列座標 int _colTo; signals: public slots: }; #endif // STEP_H
由以上程式碼可以看出Step類是用來存放走棋資訊的。
人機對戰類中過載的click函式原始碼:
void SingleGame::click(int id,int row,int col) { if(!this->_bRedTurn) return; Board::click(id,row,col); if(!this->_bRedTurn) { Step* step=getBestMove(); moveStone(step->_moveid,step->_killid,step->_rowTo,step->_colTo); } }
由以上程式碼可以看出,人類方走紅棋,電腦方走黑棋。當輪到紅方走棋的時候,與人人對戰的時候沒有區別,於是呼叫父類中的click函式;當輪到黑方走棋的時候,就獲取對電腦最有利的走棋路徑,讓電腦走棋。
--------------------------------------------------------------------這裡是華麗的分割線----------------------------------------------------------------------------
人工智慧的實現分3步走
1.獲取所有走得通的路徑
上getAllPossibleMove函式的原始碼:
void SingleGame::getAllPossibleMove(QVector<Step *>& steps)
{
//遍歷所有黑方棋子
for(int i=16;i<32;i++)
{
//如果棋子已死則直接跳過
if(_s[i]._dead) continue;
//遍歷所有行座標
for(int row=0;row<=9;row++)
{
//遍歷所有列座標
for(int col=0;col<=8;col++)
{
//獲取想要殺死的棋子的id
int killid=this->getStoneId(row,col);
//若想要殺死的棋子與行走的棋子顏色相同則跳過
if(sameColor(killid,i)) continue;
//判斷某一棋子能不能行走
if(canMove(i,killid,row,col))
{
//將可以行走的“步”存放到steps中
saveStep(i,killid,row,col,steps);
}
}
}
}
}
演算法解析:遍歷所有黑方的棋子,遍歷到某一棋子時全方位無死角地遍歷棋盤上的所有位置,把每個位置的資訊都輸入canMove函式,將canMove函式返回true的“步”存放到容器steps中。
2.從所有能走的路徑中找到對電腦最有利的路徑
上getBestMove函式的原始碼:
Step* SingleGame::getBestMove()
{
QVector<Step *> steps;
//看看有哪些步驟可以走
getAllPossibleMove(steps);
int maxScore=-100000;
Step* ret;
for(QVector<Step*>::iterator it=steps.begin();it!=steps.end();++it)
{
Step* step=*it;
//試著走一下
fakeMove(step);
//評估局面分
int score=calcScore();
//再走回來
unfakeMove(step);
//取最高的分數
if(score>maxScore)
{
maxScore=score;
ret=step;
}
}
return ret;
}
演算法解析:這便是模擬人的思維的過程,從小跟爺爺下棋的時候爺爺就對我說,要走一步看三步,然而這裡先實現走一步看一步。路邊兩個老爺爺在下棋,老爺爺雖然不會擺弄棋盤上的棋子,然而他的腦子裡是在推演的,他也會權衡一下選擇對自己最有利的路徑走棋。fakeMove和unfakeMove便是推演的過程,假想自己走一步,評估完局面分再走回來。找到走完後局面分最高的路徑作為返回值返回。
關於fakeMove函式和unfakeMove函式:
void SingleGame::fakeMove(Step* step)
{
killStone(step->_killid);
moveStone(step->_moveid,step->_killid,step->_rowTo,step->_colTo);
}
void SingleGame::unfakeMove(Step* step)
{
reliveStone(step->_killid);
moveStone(step->_moveid,step->_rowFrom,step->_colFrom);
}
其中killStone函式是用來殺死棋子的,relieveStone函式是用來複活棋子的。
獲取最優走棋路徑這一步中有個很重要的步驟就是評估局面分,上calcScore函式的原始碼:
int SingleGame::calcScore()
{
//列舉的 車=0 馬=1 炮=2 兵=3 將=4 士=5 相=6
static int chessScore[]={100,50,50,20,1500,10,10};
int redTotalScore=0;
int blackTotalScore=0;
//計算紅棋總分
for(int i=0;i<16;i++)
{
//如果棋子已死則跳過累加
if(_s[i]._dead)
continue;
redTotalScore+=chessScore[_s[i]._type];
}
//計算黑棋總分
for(int i=16;i<32;i++)
{
//如果棋子已死則跳過累加
if(_s[i]._dead)
continue;
blackTotalScore+=chessScore[_s[i]._type];
}
//返回黑棋總分-紅棋總分
return blackTotalScore-redTotalScore;
}
演算法分析:先給所有棋子分配權重,根據棋子的重要程度來分配。將是最重要的棋子,因此將的權重最高,置為1500;車其次,置為100,馬和炮再其次,置為50;兵再再其次,置為20;士和相最不重要,置為10。遍歷紅方所有的棋子,將紅方活著的棋子的權重累加出一個總分;遍歷黑方所有的棋子,將黑方活著的棋子的權重累加出一個總分。因為黑方是電腦,所以返回的局面分應該以黑方的角度計算,返回黑棋總分-紅棋總分。
3.電腦走棋
這一部分在上面的click函式中有體現,請自行往上翻。
本片部落格開始涉及到人工智慧的一些基礎演算法,當然不是特別高階,還請勿噴。感興趣的就可以自己動手實現一下啦!