新手立體四子棋AI教程(2)——價值評估函數


上一篇我們完成了整個程序的基礎框架,那么在講到真正的搜索算法前,我們先來看看五子棋如何評估當前局勢,以及如何計算某個位置的價值。

 

一、五子棋

在五子棋中,包括成五,活三,活二等定勢,下圖為山東師范大學董紅安在2005年的碩士畢業論文中使用的的評分表,可以供我們來參考。

 

0_1272525505xVDe

但是對於四子棋來說,上述評分卻並不適用,因為棋盤空間大小的原因,任何一個維度只有4子的空間,一旦沒有落成,或是任意一個位置被對方下了,那么該位置將沒有任何價值。

 

二、潛在可能性評估

 

timg-恢復的

 

我們以這張圖來舉例,當黑棋在(0,3,0)這個位置落子后,我們來分析之后的可能性。

首先在xy平面內,有如下三種方式取勝:

 

timg-恢復的2

除xy平面內之外,我們還需要考慮立體斜着四子連成的情況:

 

 

timg3

最后還要考慮垂直四子連成的情況:

image

 

並且在計算價值時,我們要注意,在上述任何一種情況中,只要預計的路線上有對方棋子出現,那么這條線(僅僅是單條線,不是整體)的評分將為0,因為他已經不能實現勝利。

 

三、展平化

為了更方便的計算,我們通過將chessBoard[x][y][z]符合規則的任意連續的四個子編入序列,並通過計數的方式實現計分。

未標題-1

我們通過一段代碼來了解這個過程:

struct flatData{
    int a;
    int b;
    int c;
    int d;
};

typedef std::vector<flatData> PicesFlatDataList;

int isWin(int board[4][4][4]);

int ChessBoard::isWin(int board[4][4][4])
{
    PicesFlatDataList flats;

    for(int y = 0;y < 4;y++)
    {
        for(int z = 0;z < 4;z++)
        {
            flatData data;
            data.a = board[0][y][z];
            data.b = board[1][y][z];
            data.c = board[2][y][z];
            data.d = board[3][y][z];
            flats.push_back(data);
        }
    }

    for(int x = 0;x < 4;x++)
    {
        for(int z = 0;z < 4;z++)
        {
            flatData data;
            data.a = board[x][0][z];
            data.b = board[x][1][z];
            data.c = board[x][2][z];
            data.d = board[x][3][z];
            flats.push_back(data);
        }
    }

    for(int x = 0;x < 4;x++)
    {
        for(int y = 0;y < 4;y++)
        {
            flatData data;
            data.a = board[x][y][0];
            data.b = board[x][y][1];
            data.c = board[x][y][2];
            data.d = board[x][y][3];
            flats.push_back(data);
        }
    }

    for(int y = 0;y < 4;y++)
    {
        flatData data;
        data.a = board[0][y][0];
        data.b = board[1][y][1];
        data.c = board[2][y][2];
        data.d = board[3][y][3];
        flats.push_back(data);
    }

    for(int x = 0;x < 4;x++)
    {
        flatData data;
        data.a = board[x][0][0];
        data.b = board[x][1][1];
        data.c = board[x][2][2];
        data.d = board[x][3][3];
        flats.push_back(data);
    }

    for(int y = 0;y < 4;y++)
    {
        flatData data;
        data.a = board[0][y][3];
        data.b = board[1][y][2];
        data.c = board[2][y][1];
        data.d = board[3][y][0];
        flats.push_back(data);
    }

    for(int x = 0;x < 4;x++)
    {
        flatData data;
        data.a = board[x][0][3];
        data.b = board[x][1][2];
        data.c = board[x][2][1];
        data.d = board[x][3][0];
        flats.push_back(data);
    }

    for(int z = 0;z < 4;z++)
    {
        flatData data;
        data.a = board[0][0][z];
        data.b = board[1][1][z];
        data.c = board[2][2][z];
        data.d = board[3][3][z];
        flats.push_back(data);
    }

    for(int z = 0;z < 4;z++)
    {
        flatData data;
        data.a = board[3][0][z];
        data.b = board[2][1][z];
        data.c = board[1][2][z];
        data.d = board[0][3][z];
        flats.push_back(data);
    }

    flatData data;
    data.a = board[0][0][0];
    data.b = board[1][1][1];
    data.c = board[2][2][2];
    data.d = board[3][3][3];
    flats.push_back(data);

    data.a = board[0][0][3];
    data.b = board[1][1][2];
    data.c = board[2][2][1];
    data.d = board[3][3][0];
    flats.push_back(data);

    data.a = board[3][0][0];
    data.b = board[2][1][1];
    data.c = board[1][2][2];
    data.d = board[0][3][3];
    flats.push_back(data);

    data.a = board[3][0][3];
    data.b = board[2][1][2];
    data.c = board[1][2][1];
    data.d = board[0][3][0];
    flats.push_back(data);
}

 

不難看出邏輯簡單粗暴,直接遍歷整個棋盤,並且將所有橫豎斜的可能性加入FlatData的abcd四個位置中,再把該條加入到整個list中,為后續其他功能提供數據。

 

四、局勢評分及輸贏判斷

在上一步的基礎上,我們要做的是根據每組FlatData(展平后的數據格式)來給出我們評估的分數。

int whiteNum = 0,blackNum = 0;

    for(auto iter = flats.begin();iter != flats.end();iter++)
    {
        whiteNum = 0;
        blackNum = 0;

        if(iter->a == chessPicesStatus::black)
            blackNum++;
        else if(iter->a == chessPicesStatus::white)
            whiteNum++;

        if(iter->b == chessPicesStatus::black)
            blackNum++;
        else if(iter->b == chessPicesStatus::white)
            whiteNum++;

        if(iter->c == chessPicesStatus::black)
            blackNum++;
        else if(iter->c == chessPicesStatus::white)
            whiteNum++;

        if(iter->d == chessPicesStatus::black)
            blackNum++;
        else if(iter->d == chessPicesStatus::white)
            whiteNum++;

        if(whiteNum == 4)
            return chessPicesStatus::white;

        if(blackNum == 4)
            return chessPicesStatus::black;

    }

    return chessPicesStatus::empty;

以上為判斷輸贏的代碼,可以看出就是在上一步的基礎上添加了abcd四個位置白棋黑棋的統計,並且判斷是否已經獲勝。對於獲取當前局面分評估的邏輯,其實只是在最后一步統計的時候更加細分一些:

int ChessBoard::getFlatPicesValue(PicesFlatDataList flats, chessPicesStatus status)
{
    int value = 0,whiteNum = 0,blackNum = 0;

    for(auto iter = flats.begin();iter != flats.end();iter++)
    {
        whiteNum = 0;
        blackNum = 0;

        if(iter->a == chessPicesStatus::black)
            blackNum++;
        else if(iter->a == chessPicesStatus::white)
            whiteNum++;

        if(iter->b == chessPicesStatus::black)
            blackNum++;
        else if(iter->b == chessPicesStatus::white)
            whiteNum++;

        if(iter->c == chessPicesStatus::black)
            blackNum++;
        else if(iter->c == chessPicesStatus::white)
            whiteNum++;

        if(iter->d == chessPicesStatus::black)
            blackNum++;
        else if(iter->d == chessPicesStatus::white)
            whiteNum++;

        if(status == chessPicesStatus::white)
        {
            //Calculating White Picess

            if(blackNum != 0)
                continue;

            if(whiteNum == 0)
                value += 1;
            else if(whiteNum == 1)
                value += 5;
            else if(whiteNum == 2)
                value += 100;
            else if(whiteNum == 3)
                value += 5000;
            else if(whiteNum == 4)
            {
                value += 100000;
            }

        }
        else
        {
            //Calculating Black Picess

            if(whiteNum != 0)
                continue;

            if(blackNum == 0)
                value += 1;
            else if(blackNum == 1)
                value += 5;
            else if(blackNum == 2)
                value += 100;
            else if(blackNum == 3)
                value += 5000;
            else if(blackNum == 4)
            {
                value += 100000;
            }

        }

        //cout<<iter->a<<" "<<iter->b<<" "<<iter->c<<" "<<iter->d<<" "<<value<<endl;
    }
    return value;
}

可以看出我們對於任何一個FlatData,如果該條數據有對方棋子存在,價值即為零,進入下一條。價值從本方棋子數目為零到四分別為1,5,100,5000,10000。

 

五、單個位置價值評分

 

為了實現后續的啟發式搜索算法,我們需要計算出每個可下位置的得分,單個位置價值評分大體邏輯與上面兩步基本一樣,是先獲取當前棋盤可落子的位置,接着遍歷與之相連的各種可能性。但由於立體四子棋支持斜着四子連成,所以需要額外注意,此時展開需要分為SimpleFlat與Non-SimpleFlat。具體區別在於:

image

紅色位置需要計算斜着連成的各種情況,而黑色區域的棋子不需要。所以在遍歷時如果判斷需要Non-SimpleFlat,則需要把更多的(斜着的若干種可能)添加到list中進行計算。

bool ifSimpleFlat(PicesPos pos)
{
    if(pos.x == 1 && pos.y == 0 ||
       pos.x == 2 && pos.y == 0 ||
       pos.x == 0 && pos.y == 1 ||
       pos.x == 3 && pos.y == 1 ||
       pos.x == 0 && pos.y == 2 ||
       pos.x == 3 && pos.y == 2 ||
       pos.x == 1 && pos.y == 3 ||
       pos.x == 2 && pos.y == 3)
        return true;
    else
        return false;
}

int ChessBoard::getPosValue(int board[4][4][4],PicesPos *pos, chessPicesStatus side)
{
    PicesFlatDataList flatList;

   // cout<<"x:"<<pos->x<<" y:"<<pos->y<<" z:"<<pos->z<<endl;
    if(ifSimpleFlat(*pos))
    {
        /*
        [0]   -
        [1]   |
        [2]   /-
        [3]   -\
        [4]   /_
        [5]   _\
        */
        flatData temp0;
        temp0.a = board[0][pos->y][pos->z];
        temp0.b = board[1][pos->y][pos->z];
        temp0.c = board[2][pos->y][pos->z];
        temp0.d = board[3][pos->y][pos->z];
        flatList.push_back(temp0);

        flatData temp1;
        temp1.a = board[pos->x][0][pos->z];
        temp1.b = board[pos->x][1][pos->z];
        temp1.c = board[pos->x][2][pos->z];
        temp1.d = board[pos->x][3][pos->z];
        flatList.push_back(temp1);

        flatData temp2;
        temp2.a = board[0][pos->y][0];
        temp2.b = board[1][pos->y][1];
        temp2.c = board[2][pos->y][2];
        temp2.d = board[3][pos->y][3];
        flatList.push_back(temp2);

        flatData temp3;
        temp3.a = board[0][pos->y][3];
        temp3.b = board[1][pos->y][2];
        temp3.c = board[2][pos->y][1];
        temp3.d = board[3][pos->y][0];
        flatList.push_back(temp3);

        flatData temp4;
        temp4.a = board[pos->x][0][0];
        temp4.b = board[pos->x][1][1];
        temp4.c = board[pos->x][2][2];
        temp4.d = board[pos->x][3][3];
        flatList.push_back(temp4);

        flatData temp5;
        temp5.a = board[pos->x][0][3];
        temp5.b = board[pos->x][1][2];
        temp5.c = board[pos->x][2][1];
        temp5.d = board[pos->x][3][0];
        flatList.push_back(temp5);

        flatData temp6;
        temp6.a = board[pos->x][pos->y][0];
        temp6.b = board[pos->x][pos->y][1];
        temp6.c = board[pos->x][pos->y][2];
        temp6.d = board[pos->x][pos->y][3];
        flatList.push_back(temp6);
    }
    else
    {
        /*
        [0]   -
        [1]   |
        [2]   /-
        [3]   -\
        [4]   /_
        [5]   _\
        [6]   -|/
        [7]   -|\
        */
        flatData temp0;
        temp0.a = board[0][pos->y][pos->z];
        temp0.b = board[1][pos->y][pos->z];
        temp0.c = board[2][pos->y][pos->z];
        temp0.d = board[3][pos->y][pos->z];
        flatList.push_back(temp0);

        flatData temp1;
        temp1.a = board[pos->x][0][pos->z];
        temp1.b = board[pos->x][1][pos->z];
        temp1.c = board[pos->x][2][pos->z];
        temp1.d = board[pos->x][3][pos->z];
        flatList.push_back(temp1);

        flatData temp2;
        temp2.a = board[0][pos->y][0];
        temp2.b = board[1][pos->y][1];
        temp2.c = board[2][pos->y][2];
        temp2.d = board[3][pos->y][3];
        flatList.push_back(temp2);

        flatData temp3;
        temp3.a = board[0][pos->y][3];
        temp3.b = board[1][pos->y][2];
        temp3.c = board[2][pos->y][1];
        temp3.d = board[3][pos->y][0];
        flatList.push_back(temp3);

        flatData temp4;
        temp4.a = board[pos->x][0][0];
        temp4.b = board[pos->x][1][1];
        temp4.c = board[pos->x][2][2];
        temp4.d = board[pos->x][3][3];
        flatList.push_back(temp4);

        flatData temp5;
        temp5.a = board[pos->x][0][3];
        temp5.b = board[pos->x][1][2];
        temp5.c = board[pos->x][2][1];
        temp5.d = board[pos->x][3][0];
        flatList.push_back(temp5);

        flatData temp6;
        temp6.a = board[pos->x][pos->y][0];
        temp6.b = board[pos->x][pos->y][1];
        temp6.c = board[pos->x][pos->y][2];
        temp6.d = board[pos->x][pos->y][3];
        flatList.push_back(temp6);

        if(pos->x == 0 && pos->y == 0 ||
           pos->x == 1 && pos->y == 1 ||
           pos->x == 2 && pos->y == 2 ||
           pos->x == 3 && pos->y == 3)
        {
            flatData temp7;
            temp7.a = board[0][0][0];
            temp7.b = board[1][1][1];
            temp7.c = board[2][2][2];
            temp7.d = board[3][3][3];
            flatList.push_back(temp7);

            flatData temp8;
            temp8.a = board[0][0][3];
            temp8.b = board[1][1][2];
            temp8.c = board[2][2][1];
            temp8.d = board[3][3][0];
            flatList.push_back(temp8);
        }
        else
        {
            flatData temp7;
            temp7.a = board[3][0][0];
            temp7.b = board[2][1][1];
            temp7.c = board[1][2][2];
            temp7.d = board[0][3][3];
            flatList.push_back(temp7);

            flatData temp8;
            temp8.a = board[3][0][3];
            temp8.b = board[2][1][2];
            temp8.c = board[1][2][1];
            temp8.d = board[0][3][0];
            flatList.push_back(temp8);
        }

    }

    int val = getFlatPicesValue(flatList,side);

    return val;

}

至此,我們已經完成了棋盤的評估函數,下一章我們將討論極值搜索算法,也就是我們真正的博弈樹函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM