象棋人工智能算法的C++實(shí)現(xiàn)(一)

前言:自AlphaGo戰(zhàn)勝世界著名九段圍棋手李世石之后,我就對(duì)棋類(lèi)人工智能產(chǎn)生了極大的興趣,并想要自己實(shí)現(xiàn)象棋的人工智能。然而那個(gè)時(shí)候我還在讀高二,沒(méi)有這么深厚的代碼基礎(chǔ),所以那個(gè)時(shí)候也就只能想想了。但是現(xiàn)在不一樣了,通過(guò)學(xué)習(xí)編程,已經(jīng)可以讓我在棋類(lèi)人工智能這個(gè)領(lǐng)域向前探索了。

創(chuàng)一個(gè)小群,供大家學(xué)習(xí)交流聊天

如果有對(duì)學(xué)C++方面有什么疑惑問(wèn)題的,或者有什么想說(shuō)的想聊的大家可以一起交流學(xué)習(xí)一起進(jìn)步呀。

也希望大家對(duì)學(xué)C++能夠持之以恒

C++愛(ài)好群,

如果你想要學(xué)好C++最好加入一個(gè)組織,這樣大家學(xué)習(xí)的話(huà)就比較方便,還能夠共同交流和分享資料,給你推薦一個(gè)學(xué)習(xí)的組織:快樂(lè)學(xué)習(xí)C++組織 可以點(diǎn)擊組織二字,可以直達(dá)

首先說(shuō)明一下本系列博客描述的人工智能算法不是基于機(jī)器學(xué)習(xí)、深度學(xué)習(xí)這么高深的知識(shí),而是一種窮舉找最優(yōu)走法的算法。之所以AlphaGo不能使用這種算法戰(zhàn)勝李世石,是因?yàn)閲迤寰志謩?shì)的判斷是極為復(fù)雜的,想要窮舉所有的情況,全世界所有的計(jì)算機(jī)一起運(yùn)行一百萬(wàn)年也無(wú)法找到最優(yōu)走法。所以DeepMind團(tuán)隊(duì)的大佬就想出了另一種解決方案就是讓AlphaGo自己學(xué)習(xí)高水平棋手間的對(duì)局,從而提升AlphaGo的棋力。然而象棋的棋局判斷還是比較容易的,殺掉對(duì)面的老將就可以獲勝,殺掉對(duì)面的車(chē)馬炮等棋子就可以提高自己的勝率/降低對(duì)方的勝率。具體的算法在之后的篇章詳細(xì)講解。

實(shí)現(xiàn)本系列博客中算法的編程工具是Qt5.5.1。

既然實(shí)現(xiàn)象棋人工智能的算法的本質(zhì)是窮舉,那么就要找到所有的通路,所謂的通路就是能夠走棋的那些“路”們,走不通的那些“路”就要直接被pass掉。

1.先把棋盤(pán)抽象出來(lái),象棋棋盤(pán)有10行9列,行標(biāo)設(shè)為0~9,列標(biāo)設(shè)為0~8。以左上角的坐標(biāo)為(0,0),假設(shè)初始時(shí)上方為紅棋,下方為黑棋。則初始時(shí)所有棋子的坐標(biāo)為:

車(chē)1(紅方):(0,0);車(chē)2(紅方):(0,8);

馬1(紅方):(0,1);馬2(紅方):(0,7);

相1(紅方):(0,2);相2(紅方):(0,6);

士1(紅方):(0,3);士2(紅方):(0,5);

將(紅方):(0,4);

炮1(紅方):(2,1);炮2(紅方):(2,7);

兵1(紅方):(3,0);兵2(紅方):(3,2);兵3(紅方):(0,4);兵4(紅方):(0,6);兵5(紅方):(0,8);

注:紅方的棋子行列坐標(biāo)對(duì)應(yīng)黑方棋子的行列坐標(biāo)的關(guān)系為:紅方行號(hào)+黑方行號(hào)=9;紅方列號(hào)+黑方列號(hào)=8。

車(chē)1(黑方):(9,8);車(chē)2(黑方):(9,0);

馬1(黑方):(9,7);馬2(黑方):(9,1);

相1(黑方):(9,6);相2(黑方):(9,2);

士1(黑方):(9,5);士2(黑方):(9,3);

將(黑方):(9,4);

炮1(黑方):(7,7);炮2(黑方):(7,1);

兵1(黑方):(6,8);兵2(黑方):(6,6);兵3(黑方):(6,4);兵4(黑方):(6,2);兵5(黑方):(6,0);

下面給大家看一下棋盤(pán)類(lèi)的源代碼,里面是關(guān)于棋盤(pán)類(lèi)的一些屬性(數(shù)據(jù)成員)和需要在棋盤(pán)上進(jìn)行的一些操作(函數(shù)成員),在這里我只給大家提供一個(gè)框架,各種成員函數(shù)的具體實(shí)現(xiàn)就要靠大家開(kāi)動(dòng)腦筋了。

#ifndef?BOARD_H

#define?BOARD_H

#include?<QFrame>

#include?"Stone.h"

#include?"Step.h"

#include?<QVector>

#include?<QMouseEvent>

class?Board?:?public?QWidget

{

Q_OBJECT

public:

explicit?Board(QWidget *parent =?0);

//32顆棋子

Stone _s[32];

//棋子的像素半徑

int?_r;

//選中棋子的id

int?_selectid;

//該不該紅棋走

bool?_bRedTurn;

//保存棋子的行走步驟

QVector _steps;

//輸入行列獲取棋子的id

int?getStoneId(int?row,int?col);

//計(jì)算即將行走的棋子與某一坐標(biāo)之間有幾顆棋子

int?num_of_Stone(int?moveid,int?row,int?col);

//輸入行列坐標(biāo)判斷該坐標(biāo)上有沒(méi)有棋子

bool?beStone(int?row,int?col);

bool?canSelect(int?id);

//最基本的能不能走的判斷判斷

bool?canMove(int?moveid,int?row,int?col,int?killid);

//判斷將能不能走

bool?canMoveJIANG(int?moveid,int?row,int?col,int?killid);

//判斷士能不能走

bool?canMoveSHI(int?moveid,int?row,int?col,int?killid);

//判斷象能不能走

bool?canMoveXIANG(int?moveid,int?row,int?col,int?killid);

//判斷車(chē)能不能走

bool?canMoveCHE(int?moveid,int?row,int?col,int?killid);

//判斷馬能不能走

bool?canMoveMA(int?moveid,int?row,int?col,int?killid);

//判斷炮能不能走

bool?canMovePAO(int?moveid,int?row,int?col,int?killid);

//判斷兵能不能走

bool?canMoveBING(int?moveid,int?row,int?col,int?killid);

//嘗試函數(shù)

void?trySelectStone(int?id);

void?tryMoveStone(int?killid,?int?row,?int?col);

//判斷兩個(gè)棋子是否是同一方的

bool?sameColor(int?id1,?int?id2);

//走棋函數(shù)極其重載

void?moveStone(int?moveid,?int?killid,?int?row,?int?col);

void?moveStone(int?moveid,?int?row,?int?col);

//殺死棋子的函數(shù)

void?killStone(int?id);

//復(fù)活棋子的函數(shù)

void?reliveStone(int?id);

void?saveStep(int?moveid,?int?killid,?int?row,?int?col, QVector& steps);

//與鼠標(biāo)點(diǎn)擊有關(guān)的函數(shù)

void?mouseReleaseEvent(QMouseEvent *);

void?click(QPoint pt);

virtual?void?click(int?id,int?row,int?col);

//獲取鼠標(biāo)點(diǎn)擊位置的行列坐標(biāo)

bool?getRowCol(QPoint pt,int?&row,int?&col);

//與顯示到窗口中有關(guān)的函數(shù)

void?drawStone(QPainter& painter,int?id);

void?paintEvent(QPaintEvent *);

//輸入行列坐標(biāo) 返回像素坐標(biāo)

QPoint?center(int?row,int?col);

//輸入棋子的id 返回像素坐標(biāo)

QPoint?center(int?id);

signals:

public?slots:

};

#endif?// BOARD_H

2.再把棋子抽象出來(lái)。每個(gè)棋子都有一個(gè)id,初始時(shí)共有32枚棋子,id從0到31;棋子所具有的屬性除了id還有所處的行列位置,棋子的類(lèi)型(車(chē)馬炮將士相兵),棋子的顏色(紅/黑),棋子是否還存活著。id置為int型;棋子類(lèi)型置為枚舉類(lèi)型enum TYPE{JIANG,CHE,PAO,MA,BING,SHI,XIANG};棋子的顏色置為bool型_red,紅棋為true,黑棋為false;棋子是否還存活置為bool型,活著為true,被吃掉為false。

#ifndef?STONE_H

#define?STONE_H

#include?<QString>

class?Stone

{

public:

Stone();

//枚舉棋子的所有類(lèi)型

enum?TYPE{JIANG,CHE,PAO,MA,BING,SHI,XIANG};

//棋子所處的行

int?_row;

//棋子所處的列

int?_col;

//棋子的id

int?_id;

//棋子是否已死

bool?_dead;

//棋子是否為紅子

bool?_red;

//棋子類(lèi)型

TYPE _type;

//初始化棋子

void?init(int?id);

//獲取棋子的類(lèi)型名

QString?getText();

};

#endif?// STONE_H

3.按照象棋的規(guī)則實(shí)現(xiàn)每個(gè)棋子的走法的前期函數(shù)鋪墊。這一部分是后期人工智能算法的基礎(chǔ),因?yàn)楹笃谝獙⑺心茏叩耐ǖ摹奥贰北4嬖谝粋€(gè)C++容器(類(lèi)似于C語(yǔ)言中的數(shù)組)里。

(1)確定某個(gè)行列位置上是否存在棋子。

這個(gè)函數(shù)在后面具體棋子的走法算法中應(yīng)用的非常廣泛。例如走馬的時(shí)候需要判斷是否別了馬腿,也就是需要判定想要移動(dòng)的馬在要去的方向的正前方的位置是否有別的棋子擋住,即判斷該位置上是否存在棋子;再例如如果出現(xiàn)了“對(duì)將”的情況,需要判斷紅將和黑將之間與其在同一直線(xiàn)上的所有位置上是否存在棋子,若所有位置都不存在棋子則兩個(gè)將可以對(duì)吃。

其實(shí)現(xiàn)的原理很簡(jiǎn)單,即輸入一個(gè)行列坐標(biāo)后遍歷所有存活的棋子的行列坐標(biāo)看一下有沒(méi)有棋子與之完全吻合,若存在這樣的棋子,則表示該行列坐標(biāo)上存在棋子。

/*確定某個(gè)行列位置上是否有棋子*/

bool?Board::beStone(int?row,int?col)

{

for(int?i=0;i<32;i++)

if(_s[i]._row==row&&_s[i]._col==col&&!_s[i]._dead)

return?true;

return?false;

}

(2)計(jì)算某一棋子與某一行列坐標(biāo)之間有幾顆棋子。

這個(gè)函數(shù)主要應(yīng)用在“對(duì)將”以及車(chē)和炮的走棋算法上。例如炮如果想要隔著炮架吃掉對(duì)方的棋子就需要保證該炮與想要吃掉的對(duì)方的棋子之間有且僅有一個(gè)棋子;再例如車(chē)想要走棋到某一行列坐標(biāo)必須保證該車(chē)與想要走到的位置之間沒(méi)有棋子。

有了(1)的鋪墊,本函數(shù)的實(shí)現(xiàn)就變得容易了。首先需要判定一下即將行走的棋子的位置與目標(biāo)位置在不在同一行(列)上。如果不在同一行(列)上則直接返回-1;如果在則可以遍歷一整行(列)并調(diào)用(1)所介紹的函數(shù)beStone來(lái)統(tǒng)計(jì)即將行走的棋子與目標(biāo)位置之間棋子的個(gè)數(shù)。

//計(jì)算即將行走的棋子與某一坐標(biāo)之間有幾顆棋子 默認(rèn)返回值為-1

int?Board::num_of_Stone(int?moveid,int?row,int?col)

{

int?i;

int?sum=0;

if(_s[moveid]._row==row)

{

if(col-_s[moveid]._col>0)

for(i=_s[moveid]._col+1;i

{

if(beStone(_s[moveid]._row,i)==true)

sum++;

}

else

for(i=_s[moveid]._col-1;i>col;i--)

{

if(beStone(_s[moveid]._row,i)==true)

sum++;

}

return?sum;

}

else?if(_s[moveid]._col==col)

{

if(row-_s[moveid]._row>0)

for(i=_s[moveid]._row+1;i

{

if(beStone(i,_s[moveid]._col)==true)

sum++;

}

else

for(i=_s[moveid]._row-1;i>row;i--)

{

if(beStone(i,_s[moveid]._col)==true)

sum++;

}

return?sum;

}

//兩個(gè)棋子不在一條直線(xiàn)上

return?-1;

}

- The End -


print_r('點(diǎn)個(gè)贊吧');

var_dump('點(diǎn)個(gè)贊吧');

NSLog(@"點(diǎn)個(gè)贊吧!")

System.out.println("點(diǎn)個(gè)贊吧!");

console.log("點(diǎn)個(gè)贊吧!");

print("點(diǎn)個(gè)贊吧!");

printf("點(diǎn)個(gè)贊吧!\n");

cout << "點(diǎn)個(gè)贊吧!" << endl;

Console.WriteLine("點(diǎn)個(gè)贊吧!");

fmt.Println("點(diǎn)個(gè)贊吧!")

Response.Write("點(diǎn)個(gè)贊吧");

alert(’點(diǎn)個(gè)贊吧’)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容