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;
}
}