四国军棋引擎开发(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;
    }
   
}

详细源代码:
https://github.com/pfysw/JunQi

猜你喜欢

转载自blog.csdn.net/pfysw/article/details/83478441