考虑棋类游戏作为一个人机博弈模型,采用机器学习中对于棋盘学习的方法,给每个棋盘位置的好坏进行一个打分,几个概念:
极大极小策略:能使计算机获胜的位置,打分为1,平局是0,计算机负是-1,显然计算机要尽量让每一步的得分极大,人要让每一步的得分极小
终端位置:若某个位置能直接决定结果,那么这个位置叫做终端位置,若一个位置不是终端位置,那么该位置的值通过递归假设双方的最优棋步决定
后继位置:P的后继位置是从P走一步棋可以到达的位置,若在某个位置P计算机要开始走,那么递归计算所有后继位置的值,计算机选择最大值的一步走棋
代码如下:
/* 博弈 */
/* 计算机选择一步 */
void FindCompMove(BoardType Board, int * BestMove, int *Value)
{
int Dc, i, Response;
/* 若棋盘满了,那么是平局 */
if (FullBoard(Board))
*Value = Draw;
/* 若走一步计算机直接赢,那么标记计算机赢 */
else if (ImmediateCompWin(Board, BestMove))
*Value = CompWin;
else
{
/* 赋初始值为最小值,递归后续的所有格子,找更大的值用来更新 */
*Value = CompLoss;
for (i = 1; i <= 9; i++)
{
if (IsEmpty(Board, i))
{
/* 这3行是回溯的核心,先占位,递归,然后释放资源 */
Place(Board, i, Comp);
FindHumanMove(Board, &Dc, $Response);
Unplace(Board, i);
if (Response > *Value)
{
*Value = Response;
*BestMove = i;
}
}
}
}
}
/* 人选择一步 */
void FindHumanMove(BoardType Board, int *BestMove, int *Value)
{
int Dc, i, Response;
if (FullBoard(Board))
*Value = Draw;
else if (ImmdiateHumanWin(Board, BestMove))
*Value = CompLoss;
else
{
*Value = CompWin;
for (i = 1; i <= 9; i++)
{
if (IsEmpty(Board, i))
{
Place(Board, i);
FindHumanMove(Board, &Dc, &Response);
Unplace(Board, i);
if (Response < *Value)
{
*Value = Response;
*BestMove = i;
}
}
}
}
}
α、β裁剪:
是对博弈树的改进,如下图:
在这个博弈树中,第一层MAX表示在子节点中选择最大的,依次类推,在第一层的递归中,第一个儿子节点求值为44,递归第二个儿子,在第二个儿子内部递归调用其所有儿子,得到第一个儿子节点值为40,这个时候,它剩下的儿子都不需要处理了,因为它在所有儿子中选择最小的,最终选出来的值一定比40小,而根节点选择儿子中最大的,第一个儿子已经有44了,这个时候第二个儿子比40小,意味着它一定不会被选择,这就是α裁剪,另一种不需要处理MIN的对称情况,就是β裁剪,合称α、β裁剪,代码如下:
/* α、β裁剪 */
void FindCompMove(BoardType Board, int *BestMove, int *Value, int Alpha, int Beta)
{
int Dc, i, Response;
if (FullBoard(Board))
*Value = Draw;
else if (ImmediateCompWin(Board, BestMove))
*Value = CompWin;
else
{
*Value = Alpha;
for (i = 1; i <= 9; i++)
{
Place(Board, i);
FindHumanMove(Board, &Dc, &Response, *Value, Beta);
Unplace(Board, i);
if (Response > *Value)
{
*Value = Response;
*BestMove = i;
}
}
}
}