基于剪枝的对抗性搜索的井字棋

来源:互联网 发布:软件下载app 编辑:程序博客网 时间:2024/06/16 14:10

基于剪枝对抗搜索的井子棋报告

 

1.问题

井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,和五子棋比较类似,由于棋盘一般不画边框,格线排成井字故得名。只要一方的三个棋子连城一条线,就算胜出。

玩过这个游戏的人大都会发现,如果两个玩家都作出最好的选择,这个游戏是一定会平局的。所以,井字棋最常使用是作为儿童游戏。虽然这个游戏看上去很简单,但是它的整个过程却复杂得多。从理论上讲,井字棋一共可能有19683种现象和362880种过程。(如果不把追求获胜的判定算进去的话)当获胜导致游戏结束时,就只剩下255168种可能过程。假设其中X都是先手:那么其中131184次将为X获胜,46080次为平局,77904次为O获胜。而当无视OX的序列并消除所有对称的情况,就只剩下138种可能的结果了,其中91次是由X获胜,44次是由O获胜,只有3个独特的情况下才产生平局。

所以对于下棋着两方都是人的时候比较简单,但如果要让电脑实现一方就需要一个比较优良的算法。

 

2.原理

本文就选择使用基于剪枝的对抗搜索技术,如下图:


                              图 1

 

我们用min代表先手方。算法的原理是把下棋的所有可能建成一棵多叉树,就像图1所示。我们遍历这棵多叉树,首先计算叶子节点的启发值,再回溯计算各个节点的启发值。

对于叶子节点启发值的计算我们采用这种方法,用棋盘空的地方全部填成x所获胜的数目减去棋盘空的地方全部填成o所获胜的数目。对于其他非叶子节点的启发值用下面的函数来求。

最后根据启发值,max选启发值最大的落子方式,min选择启发值最小的落子方式。其中用到了两种剪枝的技巧。

首先是对称剪枝,如下图:

                             图2

其实x走这几个位置都是一样的,因为它是对称的,我们用1,2,3,4,5,6,7,8 ,9分别代表落子的可能。

 

 

1

2

3

4

5

6

7

8

9

 

 

 

 

 

                      

 

 

                            表1

用A代表上下对称,用B代表左右对称,用C代表\对称,用D代表/对称。如果是A的情况可能走棋的位置有{1,2,3,4,5,6}并且是在这些位置没有棋子的前提下。同理得到下表:

                   

A

1,2,3,4,5,6

B

1,2,4,5,7,8

C

1,2,3,5,6,9

D

1,2,3,4,5,7

AB

1,2,4,5

CD

1,2,3,5

ABCD

1,2,5

                               表2

然后在树的深度遍历的过程中也用到了αβ剪枝原理如下:

对于max层的结点a来说,如果已经得到了a的一个子节点a1的启发值(min层),再去搜索a其它子节点ai(min层),如果ai的某一个子节点(max层)启发值小于a1的启发值就把ai剪枝。

 

3.算法流程

  注:电脑作为后手。

1>    玩家a下一子;

2>    电脑计算a是否获胜,获胜则结束不获胜继续;

3>    电脑判断是否需要防守(就是a有没有已经两子连成一线),若需要防守就在防守位置落子然后判断电脑是否获胜,获胜结束否则跳转1>,如果不需防守就继续;

4>    根据已走的信息建立一棵搜索树(要用到对称剪枝),然后深度遍历这棵树并计算各个节点的启发值(要用到αβ剪枝)。选择启发值最大的位置落子,

判断电脑是否获胜,若获胜则结束,否则跳转1>;

4.算法实现说明

    在程序的编写过程中发现,难点是在树的创建上,要考虑到一个节点的子节点是不确定个数的,还有节点只需要确定的深度,以及之前所说的对称剪枝。

首先定义一个长度为9的全局数组来记录双方走棋的过程及当前的状态,我们参考表1,比如1,5,6,7.就是指先手下1的位置,电脑下5的位置,然后先手下6的位置,电脑下7的位置,目前下了四步棋。

对于建图的时候,把每个节点定义成如下的数据结构。

typedef struct node

{

  int step[10];    //保存下棋过程的信息

 struct  node *next[10];   //指向节点的指针

  int qifazhi;             //此节点的启发值

  int stepzhi;             //此节点最后选择下的位置

} node,*nodet;

节点指针选了10个,其中next[0]是没有意义的,然后next[1]——next[9]对应1——9的位置。当然创建搜索树的时候不会用到所有的next[i],用不到的就赋NULL;

建图用的是递归方法,伪代码如下;

creat(nodet &a,int *step,int p)

        If   p<=0   //p的作用是为了控制创建搜索树的深度

        退出

    p=p-1;

                  a=newnode;

                  给a->step[i]赋值

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

            a->next[i]=NULL;

          for(i=1;i<=9;i++)

 { 

                             参考表1与数组step;(用对成剪枝)

                                If i这一点可以落子

                                      { step[k]=i;

                                       creat(a->next[i],step,p);}

         }              

}

5.结果展示

因为这个问题不复杂,并且计算机计算能力强大,最多下成平局,先手不会获胜。




0 0
原创粉丝点击