1.说明:
这是学极大极小搜索的第二(三)天,昨天因为思路较为混乱,且对评估函数不甚了解,因此自己写出来的AI井字棋宛若ZZ,不过仔细查看了学长的PPT并且钻研了一番,总算对Minimax算法有了比较细致的理解,在参考了一个大神的博客后,我有了新的想法,于是重新打了一遍。
2.我对极大极小搜索的理解:
所谓极大极小搜索,是相对于一个对象而言、考虑两个对象的最佳招法的一种算法。
对于写的AI棋子来说,这“一个对象”就是电脑本身。
那么如何实现AI的功能呢?这里可以引进一棵树来描述:
假设轮到电脑落子,如何判断落子的最佳位置是一个难题。我们不妨设电脑是第0层(这样好对应深度是0的判定),因为井字棋有9个格,因此最多只能走9步,那么最初深度也就是8,之后每落一子,深度逐次递减。
如果第0层是电脑,那么偶数层一定是电脑,而奇数层一定是玩家,假设第8层时电脑取胜,那么第七层的玩家一定想要尽可能缩小第八层电脑的取胜优势,而第六层的电脑又想在第七层玩家缩小自己优势的前提下,找到最大优势的落子位置…..一直递减到当前深度CurrentDepth=0为止。
关于MaxMinSearch()函数:
(1)用CountBlank(POS SaveBlank[STEP])来记录空格点,在空格点的基础之上,依次假设落点,选出估值最高的位置。
(2)因为从0开始计数,所以初始的递归深度为8,我们希望保存这一步的最佳落子坐标,因此加设全局变量MarkBestPos用来记录,通过打擂台的方法,如果当前决策值为较好者并且递归的深度与当前深度相同,也就是说到了这一次决策,那么就记录下这个坐标。
关于Evaluate()评估函数:
如果电脑输赢已定,返回INT_MAX或者 –INT_MAX,否则的话,我们将一个子放下后他所能造成的局势作为评估点,它放下后所能连成的线越多,则证明它的位置越好,那么它的值就越高,考虑行、列、对角线三个方向,将其平均值作为整体估值,同时考虑对手,如果对手下在这个点,那么他会有多大危害,因为一正一负,相加即为局势的好坏,相加越大者局势越佳。
代码如下:(Alpha-Beta剪枝就留作白天的任务了)
#include
#include
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 == 0)
{
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 == 0)
{
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 == 0)
{
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 == 0)
{
printf("\n平局了!\n");
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 flag = 1;
int i, j;
int count = 0;
if (isEnd() == COMPUTER) //将自己的优势设置为无限大
return INT_MAX;
if (isEnd() == MANAGER) //将自己的劣势设置为无限大
return -INT_MAX;
for (i = 0;i < N;i++)
{
for (j = 0;j < N;j++)
{
if (chess[i][j] == 0)
TempChess[i][j] = COMPUTER; //将剩余的地方全放上电脑棋子
else
TempChess[i][j] = chess[i][j];
}
}
//以下为电脑,记录若放满棋子后连成三个棋子的数量,越多则代表位置越重要
for (i = 0;i < N;i++)
{
for (j = 0;j < N;j++)
count += chess[i][j];
count /= 3;
}
for (j = 0;j < N;j++)
{
for (i = 0;i < N;i++)
count += chess[i][j];
count /= 3;
}
count += (TempChess[0][0] + TempChess[1][1] + TempChess[2][2]) / 3;
count += (TempChess[0][2] + TempChess[1][1] + TempChess[2][0]) / 3;
//以下为玩家
for (i = 0;i < N;i++)
{
for (j = 0;j < N;j++)
{
if (chess[i][j] == 0)
TempChess[i][j] = MANAGER; //将剩余的地方全放上电脑棋子
else
TempChess[i][j] = chess[i][j];
}
}
for (i = 0;i < N;i++)
{
for (j = 0;j < N;j++)
count += chess[i][j];
count /= 3;
}
for (j = 0;j < N;j++)
{
for (i = 0;i < N;i++)
count += chess[i][j];
count /= 3;
}
count += (TempChess[0][0] + TempChess[1][1] + TempChess[2][2]) / 3;
count += (TempChess[0][2] + TempChess[1][1] + TempChess[2][0]) / 3;
return count; //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();
if (player == COMPUTER)
BestValue = -INT_MAX;
if (player == MANAGER)
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 (player == MANAGER)
{
if (Value < BestValue)
{
BestValue = Value;
if (depth == CurrentDepth)
{
BestPosMark = MarkPos;
}
}
}
else if (player == COMPUTER)
{
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--;
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;
}