1. 程式人生 > >象棋人工智能算法的C++實現(一)

象棋人工智能算法的C++實現(一)

出了 class ets 推薦 通路 知識 鼠標 現在 print

點擊上方“程序人生”,選擇“置頂公眾號”
第一時間關註程序猿(媛)身邊的故事

前言:自AlphaGo戰勝世界著名九段圍棋手李世石之後,我就對棋類人工智能產生了極大的興趣,並想要自己實現象棋的人工智能。然而那個時候我還在讀高二,沒有這麽深厚的代碼基礎,所以那個時候也就只能想想了。但是現在不一樣了,通過學習編程,已經可以讓我在棋類人工智能這個領域向前探索了。
推薦下小編的C++學習群;513801371,不管你是小白還是大牛,小編我都歡迎,不定期分享幹貨,包括小編自己整理的一份2019最新的C++和0基礎入門教程,歡迎初學和進階中的小夥伴。

每天晚上20:00都會開直播給大家分享C++知識和路線方法,群裏會不定期更新最新的教程和學習方法(進群送2019C++學習教程),大家都是學習C++的,或是轉行,或是大學生,還有工作中想提升自己能力的C++黨,如果你是正在學習C++的小夥伴可以加入學習。最後祝所有程序員都能夠走上人生巔峰,讓代碼將夢想照進現實,非常適合新手學習,有不懂的問題可以隨時問我,工作不忙的時候希望可以給大家解惑

首先說明一下本系列博客描述的人工智能算法不是基於機器學習、深度學習這麽高深的知識,而是一種窮舉找最優走法的算法。之所以AlphaGo不能使用這種算法戰勝李世石,是因為圍棋棋局局勢的判斷是極為復雜的,想要窮舉所有的情況,全世界所有的計算機一起運行一百萬年也無法找到最優走法。所以DeepMind團隊的大佬就想出了另一種解決方案就是讓AlphaGo自己學習高水平棋手間的對局,從而提升AlphaGo的棋力。然而象棋的棋局判斷還是比較容易的,殺掉對面的老將就可以獲勝,殺掉對面的車馬炮等棋子就可以提高自己的勝率/降低對方的勝率。具體的算法在之後的篇章詳細講解。

實現本系列博客中算法的編程工具是Qt5.5.1。

既然實現象棋人工智能的算法的本質是窮舉,那麽就要找到所有的通路,所謂的通路就是能夠走棋的那些“路”們,走不通的那些“路”就要直接被pass掉。

1.先把棋盤抽象出來,象棋棋盤有10行9列,行標設為0~9,列標設為0~8。以左上角的坐標為(0,0),假設初始時上方為紅棋,下方為黑棋。則初始時所有棋子的坐標為:

車1(紅方):(0,0);車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);

註:紅方的棋子行列坐標對應黑方棋子的行列坐標的關系為:紅方行號+黑方行號=9;紅方列號+黑方列號=8。

車1(黑方):(9,8);車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);

下面給大家看一下棋盤類的源代碼,裏面是關於棋盤類的一些屬性(數據成員)和需要在棋盤上進行的一些操作(函數成員),在這裏我只給大家提供一個框架,各種成員函數的具體實現就要靠大家開動腦筋了。
#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<Step*> _steps;

//輸入行列獲取棋子的id
int getStoneId(int row,int col);
//計算即將行走的棋子與某一坐標之間有幾顆棋子
int num_of_Stone(int moveid,int row,int col);
//輸入行列坐標判斷該坐標上有沒有棋子
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);
//判斷車能不能走
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);

//嘗試函數
void trySelectStone(int id);
void tryMoveStone(int killid, int row, int col);

//判斷兩個棋子是否是同一方的
bool sameColor(int id1, int id2);

//走棋函數極其重載
void moveStone(int moveid, int killid, int row, int col);
void moveStone(int moveid, int row, int col);

//殺死棋子的函數
void killStone(int id);
//復活棋子的函數
void reliveStone(int id);

void saveStep(int moveid, int killid, int row, int col, QVector<Step*>& steps);

//與鼠標點擊有關的函數
void mouseReleaseEvent(QMouseEvent *);
void click(QPoint pt);
virtual void click(int id,int row,int col);
//獲取鼠標點擊位置的行列坐標
bool getRowCol(QPoint pt,int &row,int &col);

//與顯示到窗口中有關的函數
void drawStone(QPainter& painter,int id);
void paintEvent(QPaintEvent *);
//輸入行列坐標 返回像素坐標
QPoint center(int row,int col);
//輸入棋子的id 返回像素坐標
QPoint center(int id);
signals:

public slots:
};

#endif // BOARD_H

2.再把棋子抽象出來。每個棋子都有一個id,初始時共有32枚棋子,id從0到31;棋子所具有的屬性除了id還有所處的行列位置,棋子的類型(車馬炮將士相兵),棋子的顏色(紅/黑),棋子是否還存活著。id置為int型;棋子類型置為枚舉類型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();
//枚舉棋子的所有類型
enum TYPE{JIANG,CHE,PAO,MA,BING,SHI,XIANG};
//棋子所處的行
int _row;
//棋子所處的列
int _col;
//棋子的id
int _id;
//棋子是否已死
bool _dead;
//棋子是否為紅子
bool _red;
//棋子類型
TYPE _type;
//初始化棋子
void init(int id);
//獲取棋子的類型名
QString getText();
};

#endif // STONE_H

3.按照象棋的規則實現每個棋子的走法的前期函數鋪墊。這一部分是後期人工智能算法的基礎,因為後期要將所有能走的通的“路”保存在一個C++容器(類似於C語言中的數組)裏。

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

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

其實現的原理很簡單,即輸入一個行列坐標後遍歷所有存活的棋子的行列坐標看一下有沒有棋子與之完全吻合,若存在這樣的棋子,則表示該行列坐標上存在棋子。
/確定某個行列位置上是否有棋子/
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)計算某一棋子與某一行列坐標之間有幾顆棋子。

這個函數主要應用在“對將”以及車和炮的走棋算法上。例如炮如果想要隔著炮架吃掉對方的棋子就需要保證該炮與想要吃掉的對方的棋子之間有且僅有一個棋子;再例如車想要走棋到某一行列坐標必須保證該車與想要走到的位置之間沒有棋子。

有了(1)的鋪墊,本函數的實現就變得容易了。首先需要判定一下即將行走的棋子的位置與目標位置在不在同一行(列)上。如果不在同一行(列)上則直接返回-1;如果在則可以遍歷一整行(列)並調用(1)所介紹的函數beStone來統計即將行走的棋子與目標位置之間棋子的個數。
//計算即將行走的棋子與某一坐標之間有幾顆棋子 默認返回值為-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<col;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<row;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;
}

//兩個棋子不在一條直線上
return -1;
}

  • The End -

print_r(‘點個贊吧‘);var_dump(‘點個贊吧‘);
NSLog(@"點個贊吧!")
br/>var_dump(‘點個贊吧‘);
NSLog(@"點個贊吧!")
console.log("點個贊吧!");
print("點個贊吧!");
printf("點個贊吧!\n");
cout << "點個贊吧!" << endl;
Console.WriteLine("點個贊吧!");
fmt.Println("點個贊吧!")
Response.Write("點個贊吧");
alert(’點個贊吧’)

象棋人工智能算法的C++實現(一)