实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx

上传人:b****4 文档编号:4803637 上传时间:2022-12-09 格式:DOCX 页数:17 大小:144.20KB
下载 相关 举报
实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx_第1页
第1页 / 共17页
实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx_第2页
第2页 / 共17页
实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx_第3页
第3页 / 共17页
实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx_第4页
第4页 / 共17页
实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx_第5页
第5页 / 共17页
点击查看更多>>
下载资源
资源描述

实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx

《实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx》由会员分享,可在线阅读,更多相关《实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx(17页珍藏版)》请在冰豆网上搜索。

实验二利用搜索过程的博弈树搜索算法编写一字棋游戏.docx

实验二利用搜索过程的博弈树搜索算法编写一字棋游戏

实验二:

利用α-β搜索过程的博弈树搜索算法编写一字棋游戏

一、实验目的与要求

(1)了解极大极小算法的原理和使用方法,并学会用α-β剪枝来提高算法的效率。

(2)使用C语言平台,编写一个智能井字棋游戏。

(3)结合极大极小算法的使用方法和α-β剪枝,让机器与人对弈时不但有智能的特征,而且计算的效率也比较高。

二、实验原理

一字棋游戏是一个流传已久的传统游戏。

游戏由两个人轮流来下,分别用“X”和“O”来代替自身的棋子。

棋盘分9个格,双方可以在轮到自己下的时候,可以用棋子占领其中一个空的格子。

如果双方中有一方的棋子可以连成一条直线,则这一方判胜,对方判负。

当所有的格子都被占领,但双方都无法使棋子连成一条直线的话,则判和棋。

这是一个智能型的一字棋游戏,机器可以模拟人与用户对弈。

当轮到机器来下的时候,机器会根据当前棋局的形势,利用极大极小算法算出一个评价值,判断如何下才对自身最有利,同时也是对方来说对不利的,然后下在评价值最高的地方。

另外利用α-β剪枝,使机器在搜索评价值的时候不用扩展不必要的结点,从而提高机器计算的效率。

在用户界面方法,用一个3×3的井字格来显示用户与机器下的结果。

当要求用户输入数据的时候会有提示信息。

用户在下的过程中可以中途按下“0”退出。

当用户与计算机分出了胜负后,机器会显示出比赛的结果,并按任意键退出。

如果用户在下棋的过程中,输入的是非法字符,机器不会做出反应。

三、实验步骤和过程

1.α-β搜索过程

  在极小极大搜索方法中,由于要先生成指定深度以内的所有节点,其节点数将随着搜索深度的增加承指数增长。

这极大地限制了极小极大搜索方法的使用。

能否在搜索深度不变的情况下,利用已有的搜索信息减少生成的节点数呢?

设某博弈问题如下图所示,应用极小极大方法进行搜索

MINIMAX过程是把搜索树的生成和格局估值这两个过程分开来进行,即先生成全部搜索树,然后再进行端节点静态估值和倒推值计算,这显然会导致低效率。

如图1中,其中一个MIN节点要全部生成A、B、C、D四个节点,然后还要逐个计算其静态估值,最后在求倒推值阶段,才赋给这个MIN节点的倒推值-∞。

其实,如果生成节点A后,马上进行静态估值,得知f(A)=-∞之后,就可以断定再生成其余节点及进行静态计算是多余的,可以马上对MIN节点赋倒推值-∞,而丝毫不会影响MAX的最好优先走步的选择。

这是一种极端的情况,实际上把生成和倒推估值结合起来进行,再根据一定的条件判定,有可能尽早修剪掉一些无用的分枝,同样可获得类似的效果,这就是α-β过程的基本思想。

2.利用α-β搜索过程的一字棋的实例

图2一字棋第一阶段α-β剪枝方法

为了使生成和估值过程紧密结合,采用有界深度优先策略进行搜索,这样当生成达到规定深度的节点时,就立即计算其静态估值函数,而一旦某个非端节点有条件确定其倒推值时就立即计算赋值。

从图2中标记的节点生成顺序号(也表示节点编号)看出,生成并计算完第6个节点后,第1个节点倒推值完全确定,可立即赋给倒推值-1。

这时对初始节点来说,虽然其他子节点尚未生成,但由于s属极大值层,可以推断其倒推值不会小于-1,我们称极大值层的这个下界值为α,即可以确定s的α=-1。

这说明s实际的倒推值决不会比-1更小,还取决于其他后继节点的倒推值,因此继续生成搜索树。

当第8个节点生成出来并计算得静态估值为-1后,就可以断定第7个节点的倒推值不可能大于-1,我们称极小值层的这个上界值为β,即可确定节点7的β=-1。

有了极小值层的β值,很容易发现若α≥β时,节点7的其他子节点不必再生成,这不影响高一层极大值的选取,因s的极大值不可能比这个β值还小,再生成无疑是多余的,因此可以进行剪枝。

这样一来,只要在搜索过程记住倒推值的上下界并进行比较,就可以实现修剪操作,称这种操作为α剪枝。

类似的还有β剪枝,统称为α-β剪枝技术。

在实际修剪过程中,α、β还可以随时修正,但极大值层的倒推值下界α永不下降,实际的倒推值取其后继节点最终确定的倒推值中最大的一个倒推值。

而极小值层的倒推值上界β永不上升,其倒推值则取后继节点最终确定的倒推值中最小的一个倒推值。

2.1在进行α-β剪枝时,应注意以下几个问题:

  

(1)比较都是在极小节点和极大节点间进行的,极大节点和极大节点的比较,或者极小节点和极小节点间的比较是无意义的。

  

(2)在比较时注意是与"先辈层"节点比较,不只是与父辈节点比较。

当然,这里的"先辈层"节点,指的是那些已经有了值的节点。

  (3)当只有一个节点的"固定"以后,其值才能够向其父节点传递。

  (4)α-β剪枝方法搜索得到的最佳走步与极小极大方法得到的结果是一致的,α-β剪枝并没有因为提高效率,而降低得到最佳走步的可能性。

  (5)在实际搜索时,并不是先生成指定深度的搜索图,再在搜索图上进行剪枝。

如果这样,就失去了α-β剪枝方法的意义。

在实际程序实现时,首先规定一个搜索深度,然后按照类似于深度优先搜索的方式,生成节点。

在节点的生成过程中,如果在某一个节点处发生了剪枝,则该节点其余未生成的节点就不再生成了。

2.2α-β剪枝搜索过程(如图3)

  在搜索过程中,假定节点的生成次序是从上到下,从左到右进行的。

图中带圈的数字,表示节点的计算次序,在叙述时,为了表达上的方便,该序号也同时表示节点。

当一个节点有两个以上的序号时,不同的序号,表示的是同一个节点在不同次序下计算的结果。

过程如下:

图3α-β搜索过程的博弈树

图3给出一个d=4的博弈树的α-β搜索过程,其中带圆圈的数字表示求静态估值及倒推值过程的次序编号。

该图详细表示出α-β剪枝过程的若干细节。

初始节点的最终倒推值为1,该值等于某一个端节点的静态估值。

最好优先走步是走向右分枝节点所代表的棋局,要注意棋局的发展并不一定要沿着通向静态值为1的端节点这条路径走下去,这要看对手的实际响应而定。

2.3剪枝的效率问题

若以最理想的情况进行搜索,即对MIN节点先扩展最低估值的节点(若从左向右顺序进行,则设节点估计值从左向右递增排序),MAX先扩展最高估值的节点(设估计值从左向右递减排序),则当搜索树深度为D,分枝因数为B时,若不使用α-β剪枝技术,搜索树的端节点数

;若使用α-β剪枝技术.可以证明理想条件下生成的端节点数最少,有

(D为偶数)

(D为奇数)

比较后得出最佳α-β搜索技术所生成深度为D处的端节点数约等于不用α-β搜索技术所生成深度为D/2处的端节点数。

这就是说,在一般条件下使用α-β搜索技术,在同样的资源限制下,可以向前考虑更多的走步数,这样选取当前的最好优先走步,将带来更大的取胜优势。

四、基于α-β剪枝的一字棋源代码

#include

usingnamespacestd;

intnum=0;//记录棋盘上棋子的个数

intp,q;

inttmpQP[3][3];//表示棋盘数据的临时数组,其中的元素0表示该格为空,

intcur[3][3];//存储当前棋盘的状态

constintdepth=3;//搜索树的最大深度

voidInit()//初始化棋盘状态

{

for(inti=0;i<3;i++)

for(intj=0;j<3;j++)

{

cur[i][j]=0;

}

}

voidPrintQP()//打印棋盘当前状态

{

for(inti=0;i<3;i++)

{

for(intj=0;j<3;j++)

cout<

cout<

}

}

voidUserInput()//用户通过此函数来输入落子的位置,比如:

用户输入31,则表示用户在第3行第1列落子。

{

intpos,x,y;

L1:

cout<<"Pleaseinputyourqizi(xy):

";

cin>>pos;

x=pos/10,y=pos%10;

if(x>0&&x<4&&y>0&&y<4&&cur[x-1][y-1]==0)

{

cur[x-1][y-1]=-1;

}

else

{

cout<<"InputError!

";

gotoL1;

}

}

intCheckWin()//检查是否有一方赢棋(返回0:

没有任何一方赢;1:

计算机赢;-1:

人赢)

{//该方法没有判断平局

for(inti=0;i<3;i++)

{

if(cur[i][0]==1&&cur[i][1]==1&&cur[i][2]==1)

{

return1;

}

if(cur[i][0]==-1&&cur[i][1]==-1&&cur[i][2]==-1)

{

return-1;

}

}

for(i=0;i<3;i++)

{

if(cur[0][i]==1&&cur[1][i]==1&&cur[2][i]==1)

{

return1;

}

if(cur[0][i]==-1&&cur[1][i]==-1&&cur[2][i]==-1)

{

return-1;

}

}

if((cur[0][0]==1&&cur[1][1]==1&&cur[2][2]==1)||(cur[2][0]==1&&cur[1][1]==1&&cur[0][2]==1))

{

return1;

}

if((cur[0][0]==-1&&cur[1][1]==-1&&cur[2][2]==-1)||(cur[2][0]==-1&&cur[1][1]==-1&&cur[0][2]==-1))

{

return-1;

}

return0;

}

intvalue()//评估当前棋盘状态的值(同时可以用p或q判断是否平局)

{

p=0;

q=0;

for(inti=0;i<3;i++)//计算机一方

{//将棋盘中的空格填满自己的棋子,既将棋盘数组中的0变为1

for(intj=0;j<3;j++)

{

if(cur[i][j]==0)

{

tmpQP[i][j]=1;

}

else

{

tmpQP[i][j]=cur[i][j];

}

}

}

for(i=0;i<3;i++)//计算共有多少连成3个1的行

{

p+=(tmpQP[i][0]+tmpQP[i][1]+tmpQP[i][2])/3;

}

for(i=0;i<3;i++)//计算共有多少连成3个1的列

{

p+=(tmpQP[0][i]+tmpQP[1][i]+tmpQP[2][i])/3;

}

p+=(tmpQP[0][0]+tmpQP[1][1]+tmpQP[2][2])/3;//计算共有多少连成3个1的对角线

p+=(tmpQP[2][0]+tmpQP[1][1]+tmpQP[0][2])/3;

for(i=0;i<3;i++)//人一方

{//将棋盘中的空格填满自己的棋子,既将棋盘数组中的0变为-1

for(intj=0;j<3;j++)

{

if(cur[i][j]==0)

{

tmpQP[i][j]=-1;

}

else

{

tmpQP[i][j]=cur[i][j];

}

}

}

for(i=0;i<3;i++)//计算共有多少连成3个-1的行

{

q+=(tmpQP[i][0]+tmpQP[i][1]+tmpQP[i][2])/3;

}

for(i=0;i<3;i++)//计算共有多少连成3个1的列

{

q+=(tmpQP[0][i]+tmpQP[1][i]+tmpQP[2][i])/3;

}

q+=(tmpQP[0][0]+tmpQP[1][1]+tmpQP[2][2])/3;//计算共有多少连成3个1的对角线

q+=(tmpQP[2][0]+tmpQP[1][1]+tmpQP[0][2])/3;

returnp+q;//返回评估出的棋盘状态的值

}

intcut(int&val,intdep,boolmax)//主算法部分,实现a-B剪枝的算法,val为上一层的估计值,dep为搜索深度,max记录上一层是否为极大层

{

if(dep==depth||dep+num==9)//如果搜索深度达到最大深度,或者深度加上当前棋子数已经达到9,就直接调用估计函数

{

returnvalue();

}

inti,j,flag,temp;//flag记录本层的极值,temp记录下层求得的估计值

boolout=false;//out记录是否剪枝,初始为false

/*if(CheckWin()==1)//如果计算机赢了,就置上一层的估计值为无穷(用很大的值代表无穷)

{

val=10000;

return0;

}*/

if(max)//如果上一层是极大层,本层则需要是极小层,记录flag为无穷大;反之,则为记录为负无穷大

{

flag=10000;//flag记录本层节点的极值

}

else

{

flag=-10000;

}

for(i=0;i<3&&!

out;i++)//双重循环,遍历棋盘所有位置

{

for(j=0;j<3&&!

out;j++)

{

if(cur[i][j]==0)//如果该位置上没有棋子

{

if(max)//并且上一层为极大层,即本层为极小层,轮到用户玩家走了。

{

cur[i][j]=-1;//该位置填上用户玩家棋子

if(CheckWin()==-1)//如果用户玩家赢了

{

temp=-10000;//置棋盘估计值为负无穷

}

else

{

temp=cut(flag,dep+1,!

max);//否则继续调用a-B剪枝函数

}

if(temp

{

flag=temp;

}

if(flag<=val)//如果本层极值已小于上一层的估计值,则不需搜索下去,剪枝

{

out=true;

}

}

else//如果上一层为极小层,即本层为极大层,轮到计算机走了。

{

cur[i][j]=1;//该位置填上计算机棋子

if(CheckWin()==1)//如果计算机赢了

{

temp=10000;//置棋盘估计值为无穷

}

else

{

temp=cut(flag,dep+1,!

max);//否则继续调用a-B剪枝函数

}

if(temp>flag)

{

flag=temp;

}

if(flag>=val)

{

out=true;

}

}

cur[i][j]=0;//把模拟下的一步棋还原,回溯

}

}

}

if(max)//根据上一层是否为极大层,用本层的极值修改上一层的估计值

{

if(flag>val)

{

val=flag;

}

}

else

{

if(flag

{

val=flag;

}

}

returnflag;//函数返回的是本层的极值

}

intmain()//主程序

{

intm=-10000,val=-10000,dep=1;//m用来存放最大的val

intx_pos,y_pos;//记录最佳走步的坐标

Init();

cout<<"Qipan:

"<

PrintQP();

charIsFirst;

cout<<"Doyouwantdofirst?

(y/n)";

cin>>IsFirst;

while(IsFirst!

='y'&&IsFirst!

='n')

{

cout<<"ERROR!

"<<"Doyouwantdofirst?

(y/n)";

cin>>IsFirst;

}

if(IsFirst=='n')//---------------------------------计算机先走--------------------------------------

{

L5:

for(intx=0;x<3;x++)

{

for(inty=0;y<3;y++)

{

if(cur[x][y]==0)

{

cur[x][y]=1;

cut(val,dep,1);//计算机试探的走一步棋,棋盘状态改变了,在该状态下计算出深度为dep-1的棋盘状态估计值val

if(CheckWin()==1)

{

cout<<"Thecomputerputtheqiziat:

"<

PrintQP();

cout<<"ThecomputerWIN!

GAMEOVER."<

return0;

}

if(val>m)//m要记录通过试探求得的棋盘状态的最大估计值

{

m=val;

x_pos=x;y_pos=y;

}

val=-10000;

cur[x][y]=0;

}

}

}

cur[x_pos][y_pos]=1;

val=-10000;

m=-10000;

dep=1;

cout<<"Thecomputerputtheqiziat:

"<

PrintQP();

cout<

num++;

value();

if(p==0)

{

cout<<"DOWNGAME!

"<

return0;

}

UserInput();//玩家走一步棋

PrintQP();

cout<

num++;

value();

if(p==0)

{

cout<<"DOWNGAME!

"<

return0;

}

if(CheckWin()==-1)

{

cout<<"Conguatulations!

YouWin!

GAMEOVER."<

return0;

}

gotoL5;

}

else//--------------------------------人先走-----------------------------------

{

L4:

UserInput();

PrintQP();

cout<

num++;

value();

if(q==0)

{

cout<<"DOWNGAME!

"<

return0;

}

if(CheckWin()==-1)

{

cout<<"YouWin!

GAMEOVER."<

return0;

}

for(intx=0;x<3;x++)

{

for(inty=0;y<3;y++)

{

if(cur[x][y]==0)

{

cur[x][y]=1;

cut(val,dep,1);

if(CheckWin()==1)

{

cout<<"Thecomputerputtheqiziat:

"<

PrintQP();

cout<<"ThecomputerWIN!

GAMEOVER."<

return0;

}

if(val>m)

{

m=val;

x_pos=x;y_pos=y;

}

val=-10000;

cur[x][y]=0;

}

}

}

cur[x_pos][y_pos]=1;

val=-10000;

m=-10000;

dep=1;

cout<<"Thecomputerputtheqiziat:

"<

PrintQP();

cout<

num++;

value();

if(q==0)

{

cout<<"DOWNGAME!

"<

return0;

}

gotoL4;

}

return0;

}

五实验数据

六实验心得

通过本次试验,初步了解了博弈树算法的基本过程,知道了博弈树算法的原理与步骤,能简单的运用它来设计棋牌游戏,了解极大极小算法的原理和使用方法,并学会用α-β剪枝来提高算法的效率。

对一些简单的棋牌游戏能大概上看懂它们的运行过程。

在编程序的过程中,我们也遇到了很多困难,比如,对这新知识的不熟练,掌握的不够好,有些问题不能很好的理解,但经过我们的努力,最终还是能很好的解决了。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 电力水利

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1