基于QT的中国象棋算法设计与实现论文.docx
- 文档编号:23097677
- 上传时间:2023-04-30
- 格式:DOCX
- 页数:82
- 大小:160.15KB
基于QT的中国象棋算法设计与实现论文.docx
《基于QT的中国象棋算法设计与实现论文.docx》由会员分享,可在线阅读,更多相关《基于QT的中国象棋算法设计与实现论文.docx(82页珍藏版)》请在冰豆网上搜索。
基于QT的中国象棋算法设计与实现论文
基于QT的中国象棋算法设计与实现
摘要
中国象棋发展至今已有数千年的历史了,它是中华民族智慧的结晶。
在我国,中国象棋的普及程度是其它棋类无法比拟的,大至国际、国内比赛,小至社区街道。
本文章在研究分析对局树的基础上,先后运用极大极小查找和α-β修剪对查找下一步的算法进行了改进,并对中国象棋的对弈过程进行了有益的探讨。
最后在此基础上,运用面向对象的技术,综合结构化程序设计方法,将所有的操作逻辑封装于类,实现基于对局树算法的中国象棋游戏系统。
系统使用QT开发工具,实现了一个具有一定棋力的中国象棋人机对弈和双人对战程序。
关键词:
中国象棋人工智能博弈树Alpha-Beta搜索
WiththeimplementationofChinesechessalgorithmdesignbasedonQT
Abstract
Chinesechessdevelopmenthasbeenseveralthousandyearsofhistory,anditisthewisdomoftheChinesenation.InChina,thepopularityofChinesechessboardisunmatchedbyotherlargetointernationalanddomesticcompetitions,smallcommunitystreets。
Thisarticleisbasedonresearchandanalysisonthegametree,hastofindanduseMinimaxα-βpruningalgorithmforfindingthenextimprovement,theprocessofChinesechessandchessforausefuldiscussion.
Finally,onthisbasis,theuseofobject-orientedtechnology,integratedstructuredprogrammingmethod,alloftheoperatinglogicencapsulatedinaclass-basedsystemtoachieveChinesechessgamegametreealgorithm.ThesystemusesQTdevelopmenttoolstoachievehuman-computerchessandChinesechessprogramthathasadoublebattleofchess.
Keywords:
Chinesechess;artificialintelligence;gametree;Alpha-Betasearch
1绪论
1.1中国象棋游戏设计背景和研究意义
中国象棋游戏流传至今已经有数千年的历史了,是一种古老的文化,它集文化、科学、艺术、竞技于一体,有利于开发人的智慧,锻炼人的思维,培养人的毅力,增强人的竞争意识。
自从计算机发明,向各个领域发展,到成为我们现在每天工作和生活必不可少的一部分的这个过程中,电子游戏也逐步渗入我们每个人的娱乐活动中。
在计算机已经普及的今天,对于可以用计算机进行程序编辑的人来说,开发属于自己的游戏,已经不再是梦想,中国象棋历史悠久不仅源远流长,而且基础广泛,作为一项智力运动更成为我们游戏开发的首选对象。
中国象棋是一项智力游戏,以往都是人和人下棋,现在有了计算机我们可以和计算机竞技,人可以与计算机进行对弈。
控制计算机的是人类,而人工智能是综合性很强的一门边缘学科,它的中心任务是研究如何使计算机去做那些过去只能靠人的智力才能做的工作。
因此,对游戏开发过程中的人工智能技术的研究自然也就成了业界的一个热门研究方向。
1.2国内外象棋软件发展概况
最早的象棋软件是一副可以外出携带的电子棋盘,后来升级到电视游戏机。
开始出现的一些容量很小的象棋软件如:
DOS界面《将族》、WIN31程序的《中国象棋》等等,与其说人类下不过电脑,倒不如说是没有耐性等待电脑程序慢吞吞的搜索算法,有时甚至怀疑软件是否在搜索中死掉了。
后来,网络上先后出现了真正的WINDOWS窗口界面的象棋专业高级软件《棋隐》、《象棋世家》、《象棋参谋》、《象棋奇兵》等。
总而言之,各类象棋软件既有自身的优点,也存在共通性的缺陷,如:
中局审势不够智能化,走不出弃子取势的人性化佳构,残局时智力明显低于人脑,难以走出残局例胜的必然着法等。
放眼未来,象棋软件已经走完了一波持续上涨的行情,有可能出现逐步降温的滑坡趋势。
1.3中国象棋游戏设计研究方法
本系统主要用VisualC++进行开发,里面的MFC类库,使游戏开发更加方便,并利用人工智能相关搜索算法实现人工智能的着法生成,从而完善整个游戏的功能。
该象棋人机博弈系统实现的功能主要包括:
1、选手选择(人或电脑);
2、人机对弈(人与电脑竞技);
3、悔棋、还原;
4、着法名称显示(象棋走棋规范名称)。
1.4本文的主要工作
第一部分主要介绍了中国象棋游戏开发的背景及意义、国内外象棋软件的发展概况和象棋游戏的设计研究方法;
第二部分介绍了棋局表示方法和着法生成;
第三部分介绍了走棋和博弈程序的实现;
第四部分介绍了系统的实现。
2系统的分析和设计
2.1棋盘和棋子的表示
棋盘表示就是使用一种数据结构来描述棋盘上的信息,以便程序知道博弈的状态。
棋盘的数据表示直接影响到程序的时间及空间复杂度。
为了追求更高效率,研究人员针对中国象棋提出了多种不同的表示方法。
中国象棋的棋盘表示中最典型的是用一个10×9的二维字节(byte)数组,数组中每个元素代表棋盘上的一个交点,其值表明这个交点上放置的是一个什么棋子或是没有棋子
从棋子的角度考虑,如果把棋盘看成是一个平面坐标系,可以知道每一个棋子的位置信息:
小于10的横坐标和小于11的纵坐标;又在棋盘上最多32个棋子,故可以用一个32个字节的一维数组表示所有32个棋子的位置,其中每个字节的高4位表示该棋子的横坐标,低4位表示棋子的纵坐标。
而己被吃掉的棋子用坐标范围以外的数表示。
这样棋盘信息就被装入这32个字节中。
当然也可以把棋盘看作一维的,每个元素保存直接的位置信息。
两种棋盘表示方法:
一是做一个棋盘数组;二是做一个棋子数组。
棋盘数组中由棋子的位置获得棋子的类型可以在常数时间内完成,但由棋子的类型获得棋子的位置需要遍历;在棋子数组中由棋子的类型获得棋子的位置可以在常数时间内完成,但由棋子的位置获得棋子的类型操作繁琐。
如果一个程序同时使用这两个数组,那么获得棋子的类型和棋子的位置都可以在常数时间内完成。
这就是“棋盘·棋子联系数组”,它的技术要点是:
(1)同时用棋盘数组和棋子数组表示一个局面,棋盘数组和棋子数组之间可以互相转换。
(2)随时保持这两个数组之间的联系,棋子移动时必须同时更新这两个数组。
在棋盘上删除一个棋子,需要做两个操作(分别修改棋盘数组和棋子数组)。
同样,添加一个棋子时也需要两个操作。
“棋盘·棋子联系数组“最大的优势是:
对于着法产生过程,可以逐一查找棋子数组,如果该子没有被吃掉,就产生该子的所有合理着法,由于需要查找的棋子数组的数量(每方只有16个棋子能走)比棋盘格子的数量(90个格子)少得多,因此联系数组的速度要比单纯的棋盘数组快很多。
同时“棋盘——棋子联系数组”是很多改进的棋盘的基础。
对于中国象棋棋盘局面的表示可采用传统而简单的“棋盘数组”。
即用一个9*10的数组来存储棋盘上的信息,数组的每个元素存储棋盘上是否有棋子。
这种表示方法简单易行。
按此方法棋盘的初始情形如下所示:
BYTECChessBoard[9][10]={
R,0,0,P,0,0,p,0,0,r,
H,0,C,0,0,0,0,c,0,h,
E,0,0,P,0,0,p,0,0,e,
A,0,0,0,0,0,0,0,0,a,
K,0,0,P,0,0,p,0,0,k,
A,0,0,0,0,0,0,0,0,a,
E,0,0,P,0,0,p,0,0,e,
H,0,C,0,0,0,0,c,0,h,
R,0,0,P,0,0,p,0,0,r
};
给所有棋子定义一个值:
#defineR_BEGINR_KING
#defineR_ENDR_PAWN
#defineB_BEGINB_KING
#defineB_ENDB_PAWN
#defineNOCHESS0//没有棋子
黑方:
#defineB_KING1//黑帅
#defineB_CAR2//黑车
#defineB_HORSE3//黑马
#defineB_CANON4//黑炮
#defineB_BISHOP5//黑士
#defineB_ELEPHANT6//黑象
#defineB_PAWN7//黑卒
红方:
#defineR_KING8//红将
#defineR_CAR9//红车
#defineR_HORSE10//红马
#defineR_CANON11//红炮
#defineR_BISHOP12//红士
#defineR_ELEPHANT13//红相
#defineR_PAWN14//红兵
判断颜色:
#defineIsBlack(x)(x>=B_BEGIN&&x<=B_END)//判断某个棋子是不是黑色
#defineIsRed(x)(x>=R_BEGIN&&x<=R_END)//判断某个棋子是不是红色
对于着法的表示,直接借用棋盘数组的下标来记录着法的起点和目标点。
至于是什么棋子在走,以及是否吃子、吃的是什么子,在着法结构中并不记录。
这些信息由外部读取棋盘上起点、终点的数据获得。
着法结构定义如下,其中还包含了对着法的历史得分的记录项,以供后面要讲到的“历史启发”所用。
typedefstruct
{
shortnChessID;//表明是什么棋子
CHESSMANPOSFrom;//起始位置
CHESSMANPOSTo;//走到什么位置
intScore;//走法的分数
}CHESSMOVE;
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"kernel/global.h"
#include"information.h"
usingnamespacestd;
classMainWindow:
publicQMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget*parent=0);
voidrefresh(short*);
voidcompmove();
voidend();
intPVS(short,int,int);
publicslots:
voidslotregret();
voidslotrestart();
voidselectregret();
private:
QLabel*labelMousePos;
QLabel*red;
QLabel*black1;
QLabel*black2;
QLabel*chess[32];
QLabel*eat;
info*anticheck;
QPushButton*exit;
QPushButton*restart;
QPushButton*regret;
QProgressBar*progress;
QPixmappic[34];
shortvoidmousePressEvent(QMouseEvent*e);
voidpaintEvent(QPaintEvent*);
};
/**/
/**/
#endif//MAINWINDOW_H
有了对棋盘局面和着法的表示之后,程序才能够完成以下操作:
生成所有合法着法;执行着法、撤销着法;针对某一局面进行评估。
因而,棋局表示好比是整个程序(计算机下棋引擎部分)的地基,之后所有的操作都将建立在其基础上。
2.2着法生成
在着法生成器中,采用的基本思想就是遍历整个棋盘(一个接一个地查看棋盘上的每个位置点),当发现有当前下棋方的棋子时先判断它是何种类型的棋子,然后根据其棋子类型而相应地找出其所有合法着法并存入着法队列。
这里谈到的“合法着法”包括以下几点:
1、各棋子按其行子规则行子。
诸如马跳“日”字、象走“田”字、士在九宫内斜行等等(这里需要特别注意的是卒(兵)的行子规则会随其所在位置的不同而发生变化——过河后可以左右平移)。
2、行子不能越出棋盘的界限。
当然所有棋子都不能走到棋盘的外面,同时某些特定的棋子还有自己的行棋界限,如将、士不能出九宫,象不能过河。
3、行子的半路上不能有其它子阻拦(除了炮需要隔一个子才能打子之外)以及行子的目的点不能有本方的棋子(当然不能自己吃自己了)。
4、将帅不能碰面(本程序中只在生成计算机的着法时认为将帅碰面是非法的,而对用户所走的导致将帅碰面的着法并不认为其非法,而只是产生败局罢了)。
产生了着法后要将其存入着法队列以供搜索之用,由于搜索会搜索多层(即考虑双方你来我往好几步,这样才有利于对局面进行评估以尽可能避免“目光短浅”),所以在把着法存入着法队列的时候还要同时存储该着法所属的搜索层数。
因此可以将着法队列定义为二维数组m_MoveList[8][70],其中第一个数组下标为层数,第二个数组下标为每一层的全部着法数。
关于搜索层数,设定为4,实际使用的是1到3(在界面中将其限定为1—3)。
搜索层数的增加会显著提高电脑的下棋水平(当然计算机的棋力在很大程度上也依赖于局面评估)。
在配置为1.5G,512M内存的计算机上最多只能搜索3层,再多将导致搜索时间达到令人无法容忍的地步(这里还需要特别说明的是,搜索的速度也和着法生成的效率以及局面评估的复杂度有关,因为每分析一个结点都要执行这两种操作)。
对于每一层的着法数,也就是当前下棋方针对当前局面的所有可选的合法着法,据有关数据统计在象棋实战中一般最多情况下也就五六十种。
定义第二个数组下标为70,应当可以保证十分的安全。
3博弈程序的实现
3.1搜索算法
中国象棋博弈树非常庞大,完全建立博弈树是不可能的,唯一的解决方法就是让博弈树扩展到计算机运算可以接受的深度,然后对没有分出胜负的叶子节点给出一个尽量准确的打分,表示此局面下取得胜利的可能性,这个打分是由评估函数计算给出的,将搜索树展开是着法生成的功能,而搜索引擎则是尽可能缩小树的规模,避免一切冗余的计算,这也是计算机博弈中搜索引擎研究的重点。
根据搜索策略,目前应用于计算机博弈的搜索算法一般可分为三类:
(l)穷尽搜索(ExhaustiveSearch),这种搜索是没有抛弃任何可能成为最佳路径的搜索,不存在风险,得到的最佳走法肯定是在现有评估函数下应该得到的,例如极大极小值搜索、α-β剪枝搜索及其变种等。
(2)选择性搜索(SelectiveSearch),即裁剪搜索,这种搜索略去对一些着法的搜索,
冒着有可能漏掉最佳走法的风险,而换来局部更深的搜索深度。
中国象棋中最常用裁剪算法是空着裁剪(NullMove)等。
(3)启发式搜索(Heuristicsearch)利用象棋领域的知识(启发信息)设计搜索算法,着重对走法排序,以简化和加快搜索过程。
中国象棋中的启发算法有历史启发、杀手启发、置换表等。
搜索算法对于整个下棋引擎来说都是至关重要的。
它如同程序的心脏,驱动着整个程序。
搜索算法的好坏直接影响着程序执行的效率(从某种角度上,它影响着计算机的下棋水平。
因为,计算机必须在有限的时间内完成思考,搜索速度快意味着在相同的时间内程序可以“看”得更远,“想”的更多)。
关于棋类对弈程序中的搜索算法,已有成熟的Alpha-Beta搜索算法以及其它一些辅助增强算法(还有众多基于Alpha-Beta算法的派生、变种算法)。
我们在程序中直接借鉴了Alpha-Beta搜索算法并辅以历史启发。
本节先介绍Alpha-Beta搜索算法:
在中国象棋里,双方棋手获得相同的棋盘信息。
他们轮流走棋,目的就是吃掉对方的将或帅,或者避免自己的将或帅被吃。
图3-1博弈图
又此,可以用一棵“博弈树”来表示下棋的过程——树中每一个结点代表棋盘上的一个局面,对每一个局面(结点)根据不同的走法又产生不同的局面(生出新的结点),如此不断进行下去直到再无可选择的走法,即到达叶子结点(棋局结束)。
中国象棋的博弈树的模型大概,可以把其中连接结点的线段看作是着法,不同的着法自然产生不同的局面。
该树包含三种类型的结点:
1、奇数层的中间结点(以及根结点),表示轮到红方走棋;
2、偶数层的中间结点,表示轮到黑方走棋;
3、叶子结点,表示棋局结束。
现在让计算机来下中国象棋,它应当选择一步对它最有利的着法(最终导致它取胜的着法)。
获得最佳着法的方法就是“试走”每一种可能的着法,比较它们所产生的不同后果,然后从中选出能够产生对自己最有利的局面的着法。
结合上面所讲的博弈树,若给每个结点都打一个分值来评价其对应的局面(这一任务由后面所讲的局面评估来完成),那么可以通过比较该分值的大小来判断局面的优劣。
假定甲乙两方下棋,甲胜的局面是一个极大值(一个很大的正数),那么乙胜的局面就是一个极小值(极大值的负值),和棋的局面则是零值(或是接近零的值)。
如此,当轮到甲走棋时他会尽可能地让局面上的分值大,相反轮到乙走棋时他会选尽可能地让局面上的分值小。
反映到博弈树上,即如果假设奇数层表示轮到甲方走棋,偶数层表示轮到乙方走棋。
那么由于甲方希望棋盘上的分值尽可能大,则在偶数层上会挑选分值最大的结点——偶数层的结点是甲走完一步棋之后的棋盘局面,反映了甲方对棋局形势的要求。
同样道理,由于乙方希望棋盘上的分值尽可能小,那么在奇数层上会选择分值最小的结点。
这是“最小-最大”(Minimax)的基本思想。
这样搜索函数在估值函数的协助下可以通过在奇数层选择分值最大(最小)的结点,在偶数层选择分值最小(最大)的结点的方式来搜索以当前局面为根结点、限定搜索层数以内的整棵树来获得一个最佳的着法。
然而不幸的是,博弈树相当庞大(它会成指数增长),因而搜索(限定层数以内的)整棵树是一件相当费时的工作——其时间复杂度为O(bn)。
其中b是分枝因子,即针对各种局面的合法着法的数目的平均值,n是搜索的深度。
对于中国象棋而言,在中盘时平均着法数目大约是40种左右,那么搜索4层需要检查250万条路线,搜索5层需要检查1亿条路线,搜索6层需要检查40亿条路线!
Alpha-Beta搜索能在不影响搜索精度的前提下大幅减少工作量。
因为,如果考虑到下棋是一个你来我往的交替进行并且相互“较劲”的过程。
由于每一方都会尽可能将局面导向对自己有利而对对方不利的方向(假定下棋双方对棋局有着同样的认知,即你认为对你很糟糕的局面,在你的对手看来则是对他很有利的局面),那么某些局面由于能够产生出很糟糕的局面因而根本没有再继续考虑的价值。
所以当你看到某个局面有可能产生很糟糕的局面时(确切地说这里的“很糟糕”是与之前分析的情况相比较而言的),你应当立刻停止对其剩余子结点的分析——不要对它再抱任何幻想了,如果你选择了它,那么你必将得到那个很糟糕的局面,甚至可能更糟。
这样一来便可以在很大程度上减少搜索的工作量,提高搜索效率,这称为“树的裁剪”。
下面用图来进一步说明“树的裁剪”。
为了简便起见,将博弈树进行了简化——每个结点只有三个分支,实际情况中,刚才讲过在盘中应有大约40个分支。
假定棋盘上的局面发展到了结点A,现在轮到你走棋了,你是“最大的一方”——即你希望棋局的分值尽可能的高。
用搜索两层来看一看“树的裁剪”对提高搜索效率的帮助。
图中
表示该结点要取子结点中的最大值;
表示该结点要取子结点中的最小值。
图3-2树的裁剪
首先,考察结点A的子结点B。
结点B所属的这一层是轮到你的对手——“最小者”来走棋了,目的是使得棋局的分值尽可能的小。
依次考察结点B的各个子结点,查看它们的分值(因为事先约定好了搜索两层,现在已达到搜索深度的要求了,所以就停下来调用局面评估函数来给它打分)。
结点B的第一个子结点(从左到右算起)返回-8,第二个子结点返回了-2,第三个子结点返回了2。
由于结点B这层是你的对手来做选择,假设他一定会做出明智的选择(你不能寄希望于你的对手会走出一步“昏招”),那么他会选择返回值为-2的那个结点。
-2最终也就成了从结点B传递回的值,即倘若你(现在位于结点A)选择了产生结点B的走法,使得局面发展到了结点B。
那么下一步,你的对手的选择就会使得棋局发展成为分值为-2的那个结点所表示的局面。
再来分析结点A的第二个子结点C,结点C与结点B同属一层,它依然是轮到你的对手作选择。
依次查看结点C的各个子结点的分值,其第一个子结点返回了2……。
采用“裁剪”方法。
不必再继续考察结点C的剩余子结点了,因为结点C已经够糟糕的了,不管结点C的剩余子结点有怎样的分值,它最多只能传回-8(有可能其剩余子结点中还有分值更小的结点,因而结点C还有可能传回更小的值)。
而与前面已经分析过的结点B所传回-2相比较,作为“最大一方”的你显然更不愿意看到2的局面。
所以,你当然不会选择相应的着法使得局面发展成为结点C。
因为那样的话,下一步你的对手就会带给你一个分值不高于2的局面。
由此,在不影响搜索质量的前提下避免了搜索“无价值的”结点C的剩余子结点的大量工作,从而节省了宝贵时间,为在同样机器配置下搜索更多的层数提供了可能。
“最小-最大”的思想再加上“对树的裁剪”,这就是Alpha-Beta搜索算法的核心。
最基本的Alpha-Beta算法的代码如下:
intAlphaBeta(intdepth,intalpha,intbeta)
{
if(depth==0)//如果是叶子节点(到达搜索深度要求)
returnEvaluate();//则由局面评估函数返回估值
GenerateLegalMoves();//产生所有合法着法
while(MovesLeft())//遍历所有着法
{
MakeNextMove();//执行着法
intval=-AlphaBeta(depth-1,-be
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 基于 QT 中国象棋 算法 设计 实现 论文