前两天写了关于极大极小搜索的文章,实际还是不够深入,而且有一定偏差。经过这两天的思考之后打算另写一篇来说一下我对极大极小搜索的的进一步理解(以井字棋为例)。
在这之前,首先值得注意的一点是,如果说我将初始深度赋值为9并作为参数传到函数中,并且目的是让函数一层层递归直到最后一步棋,那么我的函数传参如下:
MaxMinSearch ( Depth )——–Depth>0
可以看出,函数就是从根节点向叶子节点进行的,也就是从上向下深度依次递减,直到第Depth层深度为0为止。类比于DFS的标记数组visit变为1,井字棋也将走过的地方依次标记(虽然最后也会撤回),总在未走过的棋盘格中找最好最坏情况。就像是利用递归的方法求fib数列(虽然不太好),递归函数会被依次压栈,最后从栈底依次弹出并计算。这样,对于电脑这一方来说,上一层总会得到下一层传来的几个极小值,而电脑则会在这中间挑选出最大值。对于玩家来说则正好相反。
我们不能让递归无休止的进行下去,那么满足以下两种条件时势必会退出:
1.基于玩家和电脑全是各自的最好招法时,很有可能还未下满棋盘便已经分出胜负,那么当前层次的递归就无需再向下进行了,此时只需要根据下棋者是电脑还是玩家,直接对应的返回INT_MAX还是 –INT_MAX即可。
if (COMPUTER == isEnd() || MANAGER == isEnd())
return Evaluate();
2.如果玩家和电脑交替下棋到了最后一层,也要做出相应的评估(输 赢 平局)。
if (depth == 0)
return Evaluate();
极大极小搜索的评估函数:
int Evaluate()
{
int count = 0;
if (isEnd() == COMPUTER)
return INT_MAX;
if (isEnd() == MANAGER)
return -INT_MAX;
return count;
}
对于极大极小搜索:
引用一段话:我们把电脑方的价值总和叫做CpValue,对手方价值总和叫做OpValue,那么一个局面的估值Value就是电脑方和对手方的价值差。表达如下式:Value=CpValue-OpValue
这个Value也就是最终返回给搜索引擎的估值。
在基本的极大极小搜索的过程里估值的取得是和上式完全契合的,即无论任何时候取估值均由固定一方的值减去另一方的值。由于井字棋棋路简单,落子之后情况无非(输 赢 平)三种,因此当落子到一个地方之后,如果电脑赢。那么返回的值CpValue-OpValue应该是最大值,否则就是最小值。
负极大搜索的评估函数:
int Evaluate()
{
int count = 0;
if (isEnd() == COMPUTER)
return INT_MAX*player;
if (isEnd() == MANAGER)
return -INT_MAX*player;
return count;
}
对于负极大搜索:当前下棋者是电脑还是玩家对于结果来说是敏感的。
“原来在极大极小搜索算法中始终返回的是红方的优势,现在要改为当前走棋方的优势。”
对于赢家是电脑这一情况,如果当前下棋者就是电脑的话,那么显然这个落子点是理想的,因此返回的是正最大值;如果当前下棋者是玩家的话,那么这一点对于玩家来说就是很差劲的,因此返回负最大值。
附上负极大搜索的代码:
#include<stdio.h>
#include<windows.h>
typedef struct position {
int x;
int y;
}POS; //坐标结构体
#define N 3
#define STEP 9
#define COMPUTER 1
#define MANAGER -1
int CurrentDepth;
int player;
int chess[N][N];
int TempChess[N][N]; //虚拟的chess
int isEnd();
int Evaluate();
int CountBlank(POS SaveBlank[STEP]);
int MaxMinSearch(int depth);
void SetChess(POS MarkPos);
void RemoveChess(POS MarkPos);
void InitBoard(); //初始化边界
void WhoPlayFirst();
void ManPlay();
void ComPlay();
void DrawBoard();
POS BestPosMark;
int main()
{
int step = 0;
CurrentDepth = STEP - 1;
InitBoard();
WhoPlayFirst();
if (player == MANAGER)
{
for (step = 1;step <= STEP;)
{
ManPlay();
DrawBoard();
if (isEnd() == MANAGER)
{
printf("\n恭喜您战胜电脑!\n");
system("pause");
return 0;
}
step++;
CurrentDepth--;
if (CurrentDepth == -1)
{
printf("\n平局了!\n");
system("pause");
return 0;
}
player = (player == COMPUTER) ? MANAGER : COMPUTER;
ComPlay();
DrawBoard();
if (isEnd() == COMPUTER)
{
printf("\n很遗憾,电脑战胜了您!\n");
system("pause");
return 0;
}
step++;
CurrentDepth--;
if (CurrentDepth == -1)
{
printf("\n平局了!\n");
system("pause");
return 0;
}
player = (player == COMPUTER) ? MANAGER : COMPUTER;
}
}
if (player == COMPUTER)
{
for (step = 1;step <= STEP;)
{
ComPlay();
DrawBoard();
if (isEnd() == COMPUTER)
{
printf("\n很遗憾,电脑战胜了您!\n");
system("pause");
return 0;
}
step++;
CurrentDepth--;
if (CurrentDepth == -1)
{
printf("\n平局了!\n");
system("pause");
return 0;
}
player = (player == COMPUTER) ? MANAGER : COMPUTER;
ManPlay();
DrawBoard();
if (isEnd() == MANAGER)
{
printf("\n恭喜您战胜电脑!\n");
system("pause");
return 0;
}
step++;
CurrentDepth--;
if (CurrentDepth == -1)
{
printf("\n平局了!\n");
system("pause");
return 0;
}
player = (player == COMPUTER) ? MANAGER : COMPUTER;
}
}
return 0;
}
void DrawBoard()
{
int i, j;
for (i = 0;i < N;i++)
{
printf("-------------\n");
for (j = 0;j < N;j++)
{
if (chess[i][j] == COMPUTER)
printf("| X ");
else if (chess[i][j] == MANAGER)
printf("| O ");
else
printf("| ");
}
printf("|\n");
}
printf("-------------\n");
}
int isEnd()
{
int i, j;
int count = 0;
for (i = 0;i < N;i++) //行
{
count = 0;
for (j = 0;j < N;j++)
count += chess[i][j];
if (count == 3 || count == -3)
return count / 3;
}
for (j = 0;j < N;j++) //列
{
count = 0;
for (i = 0;i < N;i++)
count += chess[i][j];
if (count == 3 || count == -3)
return count / 3;
}
count = 0;
count = chess[0][0] + chess[1][1] + chess[2][2];
if (count == 3 || count == -3)
return count / 3;
count = chess[0][2] + chess[1][1] + chess[2][0];
if (count == 3 || count == -3)
return count / 3;
return 0;
}
int CountBlank(POS SaveBlank[STEP])
{
int i, j;
int count = 0;
for (i = 0;i < N;i++)
{
for (j = 0;j < N;j++)
{
if (chess[i][j] == 0) //若未被占
{
SaveBlank[count].x = i;
SaveBlank[count].y = j;
count++;
}
}
}
return count;
}
int Evaluate()
{
int count = 0;
if (isEnd() == COMPUTER)
return INT_MAX*player;
if (isEnd() == MANAGER)
return -INT_MAX*player;
return count;
}
void RemoveChess(POS MarkPos)
{
chess[MarkPos.x][MarkPos.y] = 0;
player = (player == COMPUTER) ? MANAGER : COMPUTER;
}
void SetChess(POS MarkPos)
{
chess[MarkPos.x][MarkPos.y] = player;
player = (player == COMPUTER) ? MANAGER : COMPUTER;
}
int MaxMinSearch(int depth)
{
int BestValue = 0;
int Value = 0;
int i, count = 0;
POS SaveBlank[STEP];
if (COMPUTER == isEnd() || MANAGER == isEnd())
return Evaluate();
if (depth == 0)
return Evaluate();
BestValue = -INT_MAX;
count = CountBlank(SaveBlank);
for (i = 0;i < count;i++)
{
POS MarkPos = SaveBlank[i];
SetChess(MarkPos);
Value = -MaxMinSearch(depth - 1);
RemoveChess(MarkPos);
if (Value > BestValue)
{
BestValue = Value;
if (depth == CurrentDepth)
{
BestPosMark = MarkPos;
}
}
}
return BestValue;
}
void ComPlay()
{
MaxMinSearch(CurrentDepth);
printf("\n电脑落子的位置为:(%d,%d)\n", BestPosMark.x + 1, BestPosMark.y + 1);
chess[BestPosMark.x][BestPosMark.y] = COMPUTER;
}
void ManPlay()
{
POS man;
printf("\n请输入您的落子位置:\n");
scanf("%d %d", &man.x, &man.y);
man.x--;
man.y--;
if (chess[man.x][man.y] || man.x<0 || man.x >= N || man.y<0 || man.y >= N) {
printf("输入位置有误,请重新输入!");
ManPlay();
return;
}
chess[man.x][man.y] = MANAGER;
}
void InitBoard()
{
int i, j;
for (i = 0;i < N;i++)
for (j = 0;j < N;j++)
TempChess[i][j] = chess[i][j] = 0;
}
void WhoPlayFirst()
{
char ch;
printf("欢迎试玩AI井字棋,请选择您的落子先后:1---先手 2---后手\n");
ch = getchar();
player = (ch == '1') ? MANAGER : COMPUTER;
}