1. 程式人生 > >象棋人工智慧演算法的C++實現(四)——人工智慧的開端

象棋人工智慧演算法的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函式中有體現,請自行往上翻。

本片部落格開始涉及到人工智慧的一些基礎演算法,當然不是特別高階,還請勿噴。感興趣的就可以自己動手實現一下啦!