三连棋作为计算机博弈的入门棋,运用alpha-beta来产生最优招法
除了发现当前局面已经必输,输出最靠前的招法,其它局面都输出必胜或者必平招法。
O
..X
.O.
X..
0 1
三连棋的估值比较直接简单,只有胜,负,平3中结局,我定义为了10,0,-10。而某一玩家发现当前局面已经胜负已出,也就说明当前玩家已经输了(color交换的问题,不可能出现对手下了一子后发现自己莫名奇妙的赢了~_~),alpha-beta模拟过程中,估值由负极大值搜索返回到父亲节点时,该节点的alpha也就是当前局面下,当前执子的玩家的局面估值,如果局面已输就估值赋值为-10;
因此估值问题只有两种局面,判断GameOver 是平局则当前color估值为0,否则当前color的局面估值就是-10;
估值问题解决好了,剩下就是要处理先后手的问题了,通过SearchGoodMove查询到先手玩家的最优招法后,如果你作为后手玩家,在此局面上再次调用一次SearchGoodMove就能够解决。还有就是必输的局面,如果发现通过alpha-beta返回的val是-10,也就是当前局面该玩家必输,则输出棋局上最靠前的招法。
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#define INF 999999
char map[5][5], player,enermy;
int fbx[10],fby[10],k;
int checkwin()//判断在未填满棋局时游戏是否已经结束
{
if (map[0][0] == map[0][1] && map[0][1] == map[0][2])
{
if (map[0][0] == player)
return 1;
else if (map[0][0] == enermy)
return 1;
}
if (map[1][0] == map[1][1] && map[1][1] == map[1][2])
{
if (map[1][0] == player)
return 1;
else if (map[1][0] == enermy)
return 1;
}
if (map[2][0] == map[2][1] && map[2][1] == map[2][2])
{
if (map[2][0] == player)
return 1;
else if (map[2][0] == enermy)
return 1;
}
if (map[0][0] == map[1][0] && map[1][0] == map[2][0])
{
if (map[0][0] == player)
return 1;
else if (map[0][0] == enermy)
return 1;
}
if (map[0][1] == map[1][1] && map[1][1] == map[2][1])
{
if (map[0][1] == player)
return 1;
else if (map[0][1] == enermy)
return 1;
}
if (map[0][2] == map[1][2] && map[1][2] == map[2][2])
{
if (map[0][2] == player)
return 1;
else if (map[0][2] == enermy)
return 1;
}
if (map[0][0] == map[1][1] && map[1][1] == map[2][2])
{
if (map[0][0] == player)
return 1;
else if (map[0][0] == enermy)
return 1;
}
if (map[0][2] == map[1][1] && map[1][1] == map[2][0])
{
if (map[0][2] == player)
return 1;
else if (map[0][2] == enermy)
return 1;
}
return 0;
}
int Evaluate()//估值:只要胜负已出,当前玩家面对的都是输的局面,因此不是平局则估值为-10
{
if (checkwin() == 1)
{
return -10;
}
else
{
return 0;
}
}
void MakeNextMove(int x, int y,int turn)//执行下棋
{
if (turn)
map[x][y] = player;
else
map[x][y] = enermy;
}
void UnMakeMove(int x, int y)//恢复棋盘
{
map[x][y] = '.';
}
int AlphaBeta(int depth, int alpha, int beta, int turn)//alpha-beta搜索
{
int i, j, val;
if (depth == 0)
{
return Evaluate();
}
if (checkwin() == 1)
{
return -10;
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (map[i][j] == '.')
{
MakeNextMove(i, j, turn);
val = -AlphaBeta(depth - 1, -beta, -alpha, 1 - turn);
UnMakeMove(i, j);
if (val >= beta)
{
return val;
}
if (val > alpha)
{
alpha = val;
}
}
}
}
return alpha;
}
void SearchGoodMove(int depth, int alpha, int beta, int turn)//寻找当前局面最佳招法并打印
{
int i, j, val;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (map[i][j] == '.')
{
MakeNextMove(i, j, turn);
val = -AlphaBeta(depth - 1, -beta, -alpha, 1 - turn);
UnMakeMove(i, j);
if (val == 10 && turn==1)//先手且必胜则输出最前招法
{
printf("%d %d\n", i, j);
return ;
}
if (turn == 0 && val==10)//后手必败则递归调用一次将后手处理为先手
{
MakeNextMove(i, j, turn);
SearchGoodMove(depth - 1, -beta, -alpha, 1 - turn);
UnMakeMove(i, j);
return ;
}
if (val >= 0 && turn==1)//先手必平则先保存起必平招法
{
fbx[k] = i;
fby[k++] = j;
}
}
}
}
if (turn == 0)
{
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (map[i][j] == '.')
{
MakeNextMove(i, j, turn);
val = -AlphaBeta(depth - 1, -INF, INF, 1 - turn);
SearchGoodMove(depth - 1, -INF, INF, 1 - turn);//后手没有必输则递归一次转换为先手
return;
}
}
}
}
if (k == 0)//没有比平招法就必输,输出第一个招法
{
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (map[i][j] == '.')
{
printf("%d %d\n", i, j);
return ;
}
}
}
}
else//有必平招法则输出第一个
printf("%d %d\n", fbx[0], fby[0]);
}
int main()
{
int i, j, dep, t;
while (~scanf("%c", &player))
{
while(player == '\n')
player = getchar();
if (player == '.')
return 0;
if (player == 'O')
enermy = 'X';
if (player == 'X')
enermy = 'O';
dep = 0;
k = 0;
for (i = 0; i < 3; i++)
{
scanf("%s", map[i]);
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
if (map[i][j] == '.')
dep++;
}
}
if (dep % 2 && player == 'X')
t = 1;
else if (dep % 2 == 0 && player == 'O')
t = 1;
else
t = 0;
memset(fbx, 0, sizeof(fbx));
memset(fby, 0, sizeof(fby));
SearchGoodMove(dep, -INF, INF, t);
}
return 0;
}