现在我们来到了五子棋ai的核心环节:极大极小搜索和α-β剪枝算法。这两个东西听上去挺高大上,但我实际中去实现之后才发现,原来也就是那么回事。
一、极大极小搜索
什么是极大极小搜索?我首先要介绍博弈树的概念。博弈树就是己方和敌方进行决策时形成的树状结构,每一个节点的分支表示当前节点可以走的各种可能的位置,每一个叶结点表示一个局面。比如说,从空棋盘开始(根节点),我进行落子,我有15x15=255种落子选择,我落子之后,形成了一颗有255个节点的博弈树,博弈树的叶结点就是一个局面。如果此时对手进行落子,对手有254种选择,那么新形成的博弈树就会有255x254个叶结点。
可以看到,博弈树是指数级别的,如果平均分支为b,博弈树层数为d(根节点为第0层),那么叶结点的总数约为b^d。
有了博弈树的概念,我们就可以实现能看几步的“聪明”ai了。那么怎么“看几步”呢?简单来说,就是遍历博弈树的所有叶结点,寻找到对ai最有利的局面,然后进行落子。在博弈树中,当ai走棋时选择对自己最有利的位置节点走,而当玩家走棋时,是ai模拟玩家选择对玩家最有利的位置节点走。由于评估函数F对局势的评分是对于ai白子来说的,所以ai走棋时选择F最大的节点,模拟玩家走棋时选择F最小的节点(F越小,对ai越不利,对玩家越有利,ai模拟玩家时是认为玩家是“聪明”的),这就是称为极大极小搜索的缘故。
二、极大极小搜索优化
1.α-β剪枝算法
这个算法的名字听起来很高大上,但是实际内涵并不难理解。
举一个很简单的例子。
max层表示这一层节点的值应该选它的子节点的最大值,min层表示这一层节点的值应该选它的子节点的最小值。
对于这样一颗博弈树,已知了叶结点d、f、g、h的值,怎么求a的值呢?首先由d、f可以知道b的值为-1,然后a搜完了b节点,搜索另一边,搜索到a-c-g,此时可以知道c的值必然是<=-2的,由于a要选最大值节点,b的值已经大于c了,没有必要再搜完c节点,那么a-c-h这一个分支就被“剪掉了”。
α-β剪枝算法中每一个节点对应有一个α和一个β,α表示目前该节点的最好下界,β表示目前该节点的最好上界。在最开始时,α为负无穷,β为正无穷。然后进行搜索,max层节点每搜索它的一个子节点,就要更新自己的α(下界),而min层节点每搜索它的一个子节点,就要更新自己的β(上界)。如果更新之后发现α>=β了,说明后面的子节点已经不需要进行搜索了,直接break剪枝掉。这就是α-β剪枝算法的全部含义。
我的代码:
struct POINTS{
//最佳落子位置,[0]分数最高,[9]分数最低
QPoint pos[10];
int score[10];//此处落子的局势分数
};
struct DECISION{
QPoint pos;//位置
int eval;//对分数的评估
};
DECISION decision;//analyse得到的最佳落子点
int chessAi::analyse(int (*board)[15], int depth, int alpha, int beta){
gameResult RESULT=evaluate(board).result;
if(depth==0||RESULT!=R_DRAW){
//如果模拟落子可以分出输赢,那么直接返回结果,不需要再搜索
if(depth==0)
POINTS P;
P=seekPoints(board);//生成最佳的可能落子位置
return P.score[0];//返回最佳位置对应的最高分
}else return evaluate(board).score;
}else if(depth%2==0){
//max层,我方(白)决策
int sameBoard[15][15];
copyBoard(board,sameBoard);</