现在的位置: 首页 > 综合 > 正文

五子棋全攻略

2013年09月12日 ⁄ 综合 ⁄ 共 2717字 ⁄ 字号 评论关闭

软件实践课的一个大作业。

算法4月15号左右就写完了,那时感觉UI两天就能写完,再加上实验室的一些项目,于是就放那了。下周一就结课了,于是趁时赶紧把UI及网络对战实现了。

一、算法部分

这部分分两个模块,一是搜索,另一是估值。

1、搜索

这部分需要用到博弈树理论以及alpha-beta极大极小搜索算法。

博弈树,简化成简单的模型就是:己方每走一步对方便有N种走法,对于对方的N种中的每一步走法自己又有相对应的M种走法,而对于自己的M种走法,对方又有H种走法......如此下去便在逻辑上形成了一颗树,因为每一步我们都想赢,在对方走的基础之上走对自己最有利的一步,这样就形成了博弈,我们称其为博弈树。其有N*M*H*........种走法,呈指数级增长。可见如果我们单纯的BFS或DFS,那将是一件多么“消磨时光”的事,说不定等他跑出来,我们的孩子会在我们的墓碑前含着泪告诉我们结果......因此我们得用一种优化的搜索。极大极小算法解决此类博弈问题的比较流行的一种搜索算法。它的基本思想用一句话概括就是:走 对方走对我们最不利的一步后 局面对我们最有利的一步。如果还是不太理解,在这个网站
的“口袋的例子”中你可以找到更详尽的描述。光这样,效率还是很低的,于是基于极大极小算法的alpha-beta优化算法诞生了。“口袋的例子”中同样也有对alpha-beta算法思想的精彩的描述。

于是乎,这样成熟的算法就有了一个框架(ACM竞赛的时候记不住的话可以当模板使用吧):

基于此算法,我又在寻找“每个合理着法”时使用了贪心算法。其思想是:先搜索一下对方的下一步,如果对方走一步即赢(包括成五连,活四,双活三等),那么我们就要将其堵住。否则寻找对自己最有利的N步(按估值函数判断是否对自己有利),且将其按估值从大到小的顺序排列,搜索时先从估值最大的开始,N是自己定义的有限步(如果是所有可能的都搜索,复杂度仍然很高)。

这样加上一个好的估值函数,一个AI还可以的算法便出来了。

 

2、估值函数

这个没有太多的技巧,只能老老实实的对棋局进行搜索。大致思想:

从(x , y)开始向左,向右,左上,右下分别搜索,搜索同色且相连的棋子个数,将其标记为已搜索,然后根据活三活四等的特征进行匹配,记录活三活四等的个数,对每一个棋型进行评价,比如一个活三300,两个活三就赢了,给100000。然后将两方的棋型分别相加后,将和相减,返回差。各个棋型可以参考这里

 

 

二、UI和网络对战

介绍这部分之前,先可悲可笑一下。以前玩别人写的五子棋,是有双人对战,可是那时在同一台电脑上!!你下过后,我在抚摸着你在鼠标上留下的体温接着下......想想都可笑,这尼玛是真正的网络对战么?

 

1、UI

UI布局没什么好说的,coding前先将UI界面在纸上大致的画一下,心里有个谱,这样写起代码才快。UI我卡在了两个问题上,充分反映了对QT的某些机制理解的不够透彻。

第一个problem:是关于paintEvent()的。当当前窗口失去焦点之后,之前所有已经绘好的棋子全部消失。看了看文档才知道,当窗口start,chang时均触发paintEvent(),而我重绘时使用的激发函数恰好是repaint(),只重绘了局部(即落棋的地方)。解决办法是:将已下的棋子的位置、颜色信息保存起来。重绘的时候for循环全部重绘。

 

第二个problem:是关于不同窗口之间信号通信的。在具体一点来说是父窗口与子窗口之间信号通信的。这个难点主要是不知怎么获得父窗口的实例,即connect( childWidget , SIGNAL() , fatherWidget , SLOT() )中的fatherWidget。最后我扒遍了QT文档,想到了两个办法:

       1、是在实例化fatherWidget的.cpp中声明一个QWidget *w = fatherWIdget.window() ,并且声明为全局变量,然后在childWidget所在的.cpp中用extern引用w就行了。
       2、是在fatherWidget中使用connect()函数,因为在childWidget是在fatherWidget中创建的,因此使用时就像这样connect( childWidget , SIGNAL() , this , SLOT() )就行了。

李浩师兄又告诉我说,在初始化子窗口时,传递父窗口的this指针,然后在子窗口里使用。我当时怎么就没想到呢?!!

 

2、网络对战

我决定用一副自己设计时的的手绘图来说明我的实现流程。

 

其是按照上面标的序号来实现连接的。

端口是举例用的,仅仅是为了说明问题。上面的框图是下面的子窗口。

 

序号1:客户端向端口1发送udp广播,发送的内容为 "标记+客户端的HostName”

序号2:服务器端坚挺的端口2接收到内容,判断一下标记,如果是发送的标记,记录客户端的HostName,并显示IP

序号3、4:当服务器端点击已经找到的IP时,向其棋局窗口发送指令,令其开始监听tcp连接

序号5:监听成功时,向子窗口发送监听成功信号

序号6:当接收到父窗口监听成功信号时,向客户端的子窗口发送tcp连接信号

序号7:客户端的子窗口向父窗口发送tcp连接信号

序号8:客户端连接服务器,当连接成功时,向子窗口发送连接成功信号

序号9:告诉服务器端子窗口tcp连接成功,令其关闭

 

需要说明的是我是先用UDP来探路,看是否能够找到服务器(或客户端连接),然后当点击某一IP时才建立TCP连接

 

三、一些感悟和一些关于QT的经验

1、必须要有敏锐的观察力,丰富的联想力,以及大胆的尝试

2、文档才是精髓!!

3、如果自己定义的UI类作为另外一个自定义UI类的成员变量时,则有时必须在此类上面声明成员变量类,用“class className;"的形式。

4、有一些莫名其妙的问题有时将build文件整个删掉就莫名其妙的好使了。

5、google才是王道!!

四、最终效果图:

 

首界面:

 

 

人机对战:

 

 

 

 

 

 设置:

 

 

 

 

 

双人对战:

 

 

建立主机:

 

 

 

寻找主机:

 

 

抱歉!评论已关闭.