1. 程式人生 > >四國軍棋引擎開發(8)主要變例提取

四國軍棋引擎開發(8)主要變例提取

alpha-beta剪枝演算法對著法的搜尋順序有比較高的要求,最好是先搜尋好的著法再搜尋壞的著法,這樣就可以最大程度的進行剪枝。

在搜尋前我們當然不知道著法的優劣,如果知道了那就不需要搜尋了,但我們搜尋時是一層層遞進的,上一層搜尋到的最佳著法在這一層來說也相對較優,提取出來之後我們就可以優先搜尋上一層的最佳著法序列,也就是主要變例。

從演算法上來說,主要變例就是value介於alpha和beta之間的著法,提取之來後在下一層優先搜尋可以有效減少搜尋量,在後續的搜尋優化中也會起到一定的作用。

軍棋的主要變例提取要比象棋複雜一些,因為軍棋中存在一些不確定的碰撞,一步棋中會多出3個可能的分叉,如果碰到這種情況,我們下一步棋選擇最大概率分支的孩子作為最佳著法。

最佳變例存放在一個連結串列裡,這個連結串列裡的元素對應每一層的最佳著法,這裡每一步棋可能有移動、吃子、打兌、撞死四種可能,用flag標記。

struct  BestMoveList
{
    MoveResult result[4];
    BestMoveList *pNext;
};
typedef struct MoveResult
{
    MoveResultData move;
    int percent;
    u8 flag;//標記是移動還是碰撞
}MoveResult;

這裡每一層都要有一個存放最佳著法的序列連結串列,下一層獲得主要變例後不能立即對上一層進行更新,因為此時無法保證返回到上一層會更新alpha值,所以先快取起來,等到回到上一層如果更新alpha值後,再把下一層的主要變例更新當前連結串列的元素。所以這裡要定義一個BestMove陣列

BestMove aBestMove[30];

其結構體如下,另外還儲存一些狀態資訊的變數

struct BestMove
{
    BestMoveList *pHead;//連結串列表頭
    BestMoveList *pNode;//用來遍歷連結串列的元素
    u8 flag1; //判斷是否已經搜尋過
    u8 flag2; //move是否不為空
    u8 mxPerFlag;
    u8 mxPerFlag1;
};

這裡解釋一下,在獲得了最佳變例後,下一層優先搜尋最佳變例的著法,在SearchBestMove中,flag1表示是否搜尋過,搜尋過後置1,下一次搜尋到這一層後就不再搜尋,flag2表示這一層是否有主要變例,新的一層是沒有主要變例的。在出現碰撞的著法後mxPerFlag表示概率最高的一種,標記下一層是否搜尋主要變例,而mxPerFlag1是表示正常搜尋時上一層如果是碰撞是否是最大概率,如果是最大概率才提取最佳著法,程式碼如下,cnt表示層數,從1開始

    if( aBestMove[cnt-1].flag2 && aBestMove[cnt-1].mxPerFlag && !aBestMove[cnt-1].flag1 )
    {
        //層層遞迴搜尋,注意遇到碰撞需要分別搜尋3種碰撞可能
    }

在每一層的搜尋裡,我們得到最大值後就更新主要變例,而不是大於alpha,因為碰撞有3種可能性,雖然當前層小於alpha,返回上層後3種可能性根據概率取平均仍然可能比alpha大。

        if( val>mxVal )
        {
            mxVal = val;
            if( aBestMove[cnt-1].mxPerFlag1 )
            {
                UpdateBestMove(aBestMove,p,depth,cnt,isHashVal);
            }
            if( val>alpha )
            {
                pBest = &p->move;
                alpha = val;
            }
        }

接下來我們來看更新主要變例的程式碼,先更新這一層的最佳著法,放在連結串列的表頭,連結串列中接下來的元素從下一層的主要變例連結串列中拷貝,如果不存在,則向連結串列中新增一個新的結點

void UpdateBestMove(
        BestMove *aBeasMove,
        MoveList *pMove,
        int depth,
        int cnt,
        u8 isHashVal)
{
    BestMoveList *p;
    BestMoveList *p1;

    if( aBeasMove[cnt-1].pHead==NULL )
    {
        aBeasMove[cnt-1].pHead = (BestMoveList*)malloc(sizeof(BestMoveList));
        memset(aBeasMove[cnt-1].pHead, 0, sizeof(BestMoveList));

    }
    p = aBeasMove[cnt-1].pHead;
    //更新當前層最佳著法,注意需要把各種可能的碰撞儲存到相應的result數組裡
    SetBestMoveNode(aBeasMove,p,pMove,depth,cnt);
    //接下去的元素從下一層連結串列中拷貝
    for(p1=aBeasMove[cnt].pHead; p1!=NULL; p1=p1->pNext)
    {
        if(p->pNext==NULL)
        {
            p->pNext = (BestMoveList*)malloc(sizeof(BestMoveList));
            memset(p->pNext, 0, sizeof(BestMoveList));
        }
        memcpy(p->pNext->result,p1->result,sizeof(p1->result));
        p = p->pNext;
    }
   
}