1、摘 要中国象棋发展至今已有数千年的历史了,它是中华民族智慧的结晶。在我国,中国象棋的普及程度是其它棋类无法比拟的,大至国际、国内比赛,小至社区街道。如今,仅中国就有2亿人会下中国象棋,且中国象棋的发展趋势日益国际化。本文首先研究了中国象棋在计算机中的表示问题,讨论如何产生着法等一系列相关内容,其次研究了博弈树的搜索技术及在此基础上发展起来的相关剪枝算法。系统使用MFC文档视图体系结构和Visual C+开发工具,实现了一个具有一定棋力的中国象棋人机对弈程序。此博弈程序实现了人机博弈,悔棋,电脑难度设置,着法名称生成等功能。 关键词:中国象棋 人工智能 博弈树 Alpha-Beta搜索目 录1
2、前言11.1中国象棋游戏设计背景和研究意义11.2国内外象棋软件发展概况11.3中国象棋游戏设计研究方法11.4本文的主要工作22棋局表示和着法生成22.1棋盘和棋子的表示22.2着法生成43走棋和博弈程序的实现53.1博弈程序的实现53.1.1搜索算法53.1.2着法排序83.1.3局面评估93.2悔棋和还原功能的实现113.3着法名称显示功能的实现123.4胜败判定144界面设计和系统实现154.1界面设计154.2系统实现175总结21参 考 文 献23ABSTRACT24致 谢25仲恺农业工程学院毕业论文(设计)成绩评定表26I1 前言1.1 中国象棋游戏设计背景和研究意义中国象棋游戏
3、流传至今已经有数千年的历史了,是一种古老的文化,它集文化、科学、艺术、竞技于一体,有利于开发人的智慧,锻炼人的思维,培养人的毅力,增强人的竞争意识。自从计算机发明,向各个领域发展,到成为我们现在每天工作和生活必不可少的一部分的这个过程中,电子游戏也逐步渗入我们每个人的娱乐活动中。在计算机已经普及的今天,对于可以用计算机进行程序编辑的人来说,开发属于自己的游戏,已经不再是梦想,中国象棋历史悠久不仅源远流长,而且基础广泛,作为一项智力运动更成为我们游戏开发的首选对象。中国象棋是一项智力游戏,以往都是人和人下棋,现在有了计算机我们可以和计算机竞技,人可以与计算机进行对弈。控制计算机的是人类,而人工智
4、能是综合性很强的一门边缘学科,它的中心任务是研究如何使计算机去做那些过去只能靠人的智力才能做的工作。因此,对游戏开发过程中的人工智能技术的研究自然也就成了业界的一个热门研究方向。 1.2 国内外象棋软件发展概况最早的象棋软件是一副可以外出携带的电子棋盘,后来升级到电视游戏机。开始出现的一些容量很小的象棋软件如:DOS界面将族、WIN31程序的中国象棋等等,与其说人类下不过电脑,倒不如说是没有耐性等待电脑程序慢吞吞的搜索算法,有时甚至怀疑软件是否在搜索中死掉了。后来,网络上先后出现了真正的WINDOWS窗口界面的象棋专业高级软件棋隐、象棋世家、象棋参谋、象棋奇兵等。总而言之,各类象棋软件既有自身
5、的优点,也存在共通性的缺陷,如:中局审势不够智能化,走不出弃子取势的人性化佳构,残局时智力明显低于人脑,难以走出残局例胜的必然着法等。放眼未来,象棋软件已经走完了一波持续上涨的行情,有可能出现逐步降温的滑坡趋势。1.3 中国象棋游戏设计研究方法本系统主要用 Visual C+ 进行开发,里面的MFC类库,使游戏开发更加方便,并利用人工智能相关搜索算法实现人工智能的着法生成,从而完善整个游戏的功能。该象棋人机博弈系统实现的功能主要包括:1、选手选择(人或电脑);2、人机对弈(人与电脑竞技);3、电脑棋力难度选择(电脑下棋能力难度选择,共有4级:按电脑配置选择难度);4、悔棋、还原;5、着法名称显
6、示(象棋走棋规范名称)。1.4 本文的主要工作第一部分主要介绍了中国象棋游戏开发的背景及意义、国内外象棋软件的发展概况和象棋游戏的设计研究方法;第二部分介绍了棋局表示方法和着法生成;第三部分介绍了走棋和博弈程序的实现;第四部分介绍了界面设计和系统的实现。2 棋局表示和着法生成2.1 棋盘和棋子的表示对于中国象棋棋盘局面的表示可采用传统而简单的“棋盘数组”。即用一个9*10的数组来存储棋盘上的信息,数组的每个元素存储棋盘上是否有棋子。这种表示方法简单易行。按此方法棋盘的初始情形如下所示: BYTE CChessBoard910 = R, 0, 0, P, 0, 0, p, 0, 0, r, H,
7、 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 ; 给所有棋子定义一个值: #define R_BEGIN R_KING #define R_END R_PAWN #defi
8、ne B_BEGIN B_KING #define B_END B_PAWN #define NOCHESS 0 /没有棋子 黑方:#define B_KING 1 /黑帅 #define B_CAR 2 /黑车 #define B_HORSE 3 /黑马 #define B_CANON 4 /黑炮 #define B_BISHOP 5 /黑士 #define B_ELEPHANT 6 /黑象 #define B_PAWN 7 /黑卒 红方:#define R_KING 8 /红将 #define R_CAR 9 /红车 #define R_HORSE 10/红马 #define R_CANO
9、N 11/红炮 #define R_BISHOP 12/红士 #define R_ELEPHANT 13/红相 #define R_PAWN 14/红兵 判断颜色: #define IsBlack(x) (x=B_BEGIN & x=R_BEGIN & x= beta)/裁剪return beta;if (val alpha)/保留最大值alpha = val;return alpha;3.1.2 着法排序Alpha-Beta搜索算法是在“最小-最大”的基础上引入“树的裁剪”的思想以期提高效率,它的效率将在很大程度上取决于树的结构如果搜索了没多久就发现可以进行“裁剪”了,那么需要分析的工作量将
10、大大减少,效率自然也就大大提高;而如果直至分析了所有的可能性之后才能做出“裁剪”操作,那此时“裁剪”也已经失去了它原有的价值(因为你已经分析了所有情况,这时的Alpha-Beta搜索已和“最小-最大”搜索别无二致了)。因而,要想保证Alpha-Beta搜索算法的效率就需要调整树的结构,即调整待搜索的结点的顺序,使得“裁剪”可以尽可能早地发生。可以根据部分已经搜索过的结果来调整将要搜索的结点的顺序。因为,通常当一个局面经过搜索被认为较好时,其子结点中往往有一些与它相似的局面(如个别无关紧要的棋子位置有所不同)也是较好的。由J.Schaeffer所提出的“历史启发”(History Heurist
11、ic)就是建立在这样一种观点之上的。在搜索的过程中,每当发现一个好的走法,就给该走法累加一个增量以记录其“历史得分”,一个多次被搜索并认为是好的走法的“历史得分”就会较高。对于即将搜索的结点,按照“历史得分”的高低对它们进行排序,保证较好的走法(“历史得分”高的走法)排在前面,这样Alpha-Beta搜索就可以尽可能早地进行“裁剪”,从而保证了搜索的效率。对于着法的排序可以使用各种排序算法,在程序中采用了归并排序。归并排序的空间复杂度为O(n),时间复杂度为O(nlog2n),具有较高的效率。3.1.3 局面评估前文已经讲过了棋局表示、着法生成、搜索算法(包括搜索辅助“历史启发”), 在象棋程
12、序中如果说搜索算法是心脏,那么局面评估就是大脑。搜索算法负责驱动整个程序,而局面评估则负责对搜索的内容进行判断和评价。因而搜索与局面评估是整个下棋引擎的核心。首先,先介绍一下在局面评估中需要考虑的因素。就不同的棋类可能要考虑的因素略有差异。在中国象棋中所要考虑的最基本的几个因素包括如下四点:1、子力总和子力是指某一棋子本身所具有的价值。通俗地讲就是一个棋子它值个什么价。例如,车值10的话,那可能马值6,卒值2等等。所以在评估局面时,首先要考虑双方的子力总和的对比。比如红方拥有士象全加车马炮,而黑方只有残士象加双马,则红方明显占优。2、棋子位置棋子位置,或称控制区域,是指某一方的棋子在棋盘上所占
13、据(控制)的位置。例如,沉底炮、过河卒、以及车占士角等都是较好的棋子位置状态,而窝心马、将离开底线等则属较差的棋子位置状态。3、棋子的机动性棋子的机动性指棋子的灵活度(可移动性)。例如,起始位置的车机动性较差,所以下棋讲究早出车。同样四面被憋马腿的死马机动性也较差(对于一步也不能走的棋子,可以认为其机动性为零)。4、棋子的相互关系这一点的分析较为复杂,因为一个棋子与其它子之间往往存在多重关系。如:一个马可能在对方的炮的攻击之下同时它又攻击着对方的车。在程序中,估值函数最后返回的是每一方的总分的差值,而各方的总分就是上面所提到的四个因素的打分的总和。对于子力打分和控制区域打分,只要遍历棋盘,当遇
14、到棋子时简单地去查事先定义好的“子力价值表”和“控制区域价值表”,取出相对应的值进行累加即可。对于机动性打分,需要求出各个子总共有多少种走法,然后根据各个子所不同的机动性价值每多一种走法就加一次相应的分数。 对棋子间相互关系的打分,要用到以下几个数据:int m_BaseValue15; /存放棋子基本价值int m_FlexValue15; /存放棋子灵活性分值short m_AttackPos109; /存放每一位置被威胁的信息BYTE m_GuardPos109; /存放每一位置被保护的信息BYTE m_FlexibilityPos109;/存放每一位置上棋子的灵活性分值int m_ch
15、essValue109; /存放每一位置上棋子的总价值其中计算机会进行所有棋子值的判断,AttackPos和GuardPos分别记录该棋子受到的威胁和被保护的值。当遍历一遍棋盘之后,子力打分、控制区域打分和机动性打分都可以完成,而关系表也可以填完。之后,再根据关系表来具体考察棋子的相互关系,进行关系打分。分析关系时,首先,对王的攻击保护应分离出来单独考虑,因为对王的保护没有任何意义,一旦王被吃掉整个游戏就结束了。其次,对一个普通子,当它既受到攻击又受到保护的时候要注意如下几个问题:1、攻击者子力小于被攻击者子力,攻击方将愿意换子。比如,一个车正遭受一个炮的攻击,那么任何对车的保护都将失去意义对
16、方肯定乐意用一个炮来换一个车。2、多攻击/单保护的情况,并且攻击者最小子力小于被攻击者子力与保护者子力之和,则攻击方可能以一子换两子。3、三攻击/两保护的情况,并且攻击者子力较小的二者之和小于被攻击者子力与保护者子力之和,则攻击方可能以两子换三子。4、攻击方与保护方数量相同,并且攻击者子力小于被攻击者子力与保护者子力之和再减去保护者中最大子力,则攻击方可能以n子换n子。当然,上述四条只是覆盖了最常见的几种情况,覆盖并不全面。而且,在程序中并没有直接地重新考虑双方兑子之后的控制区域及机动性变化情况(之所以说没有“直接地重新考虑”,是因为搜索继续展开结点后仍会考虑这些因素,只是目前不知这样效果是否
17、受影响考察这两种方法在效果上的差异需要一定数量的试验数据的支持)。所以,如果今后要对引擎进行改进,提高程序的下棋水平的话,还应当在此进行研究。3.2 悔棋和还原功能的实现悔棋和还原是棋类软件中较为基本的功能。要实现悔棋和还原功能,首先要明确哪些信息应当被保存以供悔棋和还原所使用。在程序中保存了如下信息:棋局表示中所定义的棋盘数组;各棋子的贴图位置;这里需要特别说明的是通常象棋程序处于程序效率的考虑并不保存所有棋子的信息,而只是保存之前一步的走棋信息。此后当悔棋的时候,需要撤销着法;还原的时候,需要执行着法。然而,在编写自己的程序时一来考虑到程序的可读性和不易出错性,二来考虑到对当今的计算机的配
18、置来说这点开销基本上不会对程序的效率产生什么影响。因此保存了全部棋子的信息。根据所要保存的数据定义了如下基本结构类型:typedef struct CHESSMOVE cmChessMove; short nChessID;/被吃掉的棋子UNDOMOVE;在对弈过程中,每一回合都将棋局信息(这里指前面所说的需要保存的信息)保存至走法队列,以供悔棋所用。还原功能是与悔棋功能相对应的,只有当产生了悔棋功能之后,还原功能才会被激活。一个回合的结束意味着前一次操作没有悔棋功能的产生,因此还原队列也应被清空。在悔棋中主要完成了以下任务:1、下棋回合数减一;2、将当前局面信息保存至走法队列,以供还原所用;
19、3、从走法队列中取出上一回合的棋局信息,恢复到当前局面,然后将其从队列中剔除掉;4、将显示着法名称的列表框中的本回合的着法名称保存到一个着法名称队列,以供还原所用。然后从列表框中删除它。而在还原中所做的刚好和悔棋相反:1、下棋回合数加一;2、将当前局面信息保存至队列,以供悔棋所用;3、从队列中取出最近一次悔棋前的棋局信息,恢复到当前局面,然后将其从队列中剔除;4、从走法队列中取出最近一次存入的着法名称(两项,因为每回合会产生两步着法),将其重新显示到列表框中。然后将其从中剔除。以上便是悔棋和还原功能所完成的具体操作,其代码分别写入悔棋和还原按钮(Button)的事件处理函数中。3.3 着法名称
20、显示功能的实现每当下棋者(用户或是计算机)走一步棋,在棋盘旁边的一个列表框控件(List Box)中按照中国象棋关于着法描述的规范要求显示出该着法的名称。如:炮八进四、马二进三此类。为了获得该着法名称,我们编写了一个函数,其功能就是将被移动的棋子类型以及走法的起点坐标、终点坐标这些信息转换成中国象棋所规范的着法名称。实现此功能代码如下: void CGradientProgressCtrl:OnPaint() CPaintDC dc(this); / device context for painting / TODO: Add your message handler code here i
21、f(m_nCurrentPosition=m_nUpper) CRect rect; GetClientRect(rect); CBrush brush; brush.CreateSolidBrush(:GetSysColor(COLOR_3DFACE); dc.FillRect(&rect,&brush); VERIFY(brush.DeleteObject(); return; CRect rectClient; GetClientRect(rectClient); float maxWidth(float)m_nCurrentPosition/(float)m_nUpper*(float
22、)rectClient.right); /绘制 DrawGradient(&dc,rectClient,(int)maxWidth); /显示进程条进度文字 if(m_bShowPercent) CString percent; percent.Format(%d%,(int)(100*(float)m_nCurrentPosition/m_nUpper); dc.SetTextColor(m_clrText); dc.SetBkMode(TRANSPARENT);dc.DrawText(percent,&rectClient,DT_VCENTER|DT_CENTER|DT_SINGLELIN
23、E); /显示其他文字 if(m_bIsShowText) dc.SetTextColor(m_clrText); dc.SetBkMode(TRANSPARENT); dc.DrawText(m_strShow,&rectClient,DT_VCENTER|DT_CENTER|DT_SINGLELINE); / Do not call CProgressCtrl:OnPaint() for painting messages int CGradientProgressCtrl:SetPos(int nPos) /Set the Position of the Progress m_nCurr
24、entPosition=nPos; return (CProgressCtrl:SetPos(nPos); int CGradientProgressCtrl:StepIt() m_nCurrentPosition+=m_nStep; return CProgressCtrl:StepIt(); 以下介绍如何对列表框控件(List Box)进行操作,以显示或删除着法名称。首先,在ChessDlg下定义以下函数: this-GetMoveStr(nFromX,nFromY,nToX,nToY,nSourceID); /用来获得刚下的一步棋的走法; void CChessDlg:AddChessR
25、ecord(int nFromX,int nFromY,int nToX,int nToY,int nUserChessColor,int nSourceID) /将走法添加进下棋记录; 然后,显示在Listbox中。当列表框中的项的数目超过列表框的显示范围时,列表框会自动添加垂直滚动条(前提是其VerticalScrollbar属性要为True该属性默认即为True)。但是显示的内容依然是最早加进来的项。在控件属性里选择Vertical Scroll,使得列表框可垂直滚动以显示最新的着法名称。想要从列表框中删除项时,可以使用 m_lstChessRecord.DeleteString(m_l
26、stChessRecord.GetCount()-1);减一之后正好是最后一项的行号。3.4 胜败判定胜负判定只要一方将另一方的将或帅吃掉就是胜者。主要代码如下:int CSearchEngine:IsGameOver(BYTE position9, int nDepth) int i,j; BOOL RedLive=FALSE,BlackLive=FALSE; /检查红方九宫是否有帅 for(i=7;i10;i+) for(j=3;j6;j+) if(positionij=B_KING)BlackLive=TRUE;if(positionij=R_KING)RedLive=TRUE; /检查
27、黑方九宫是否有将 for(i=0;i3;i+) for(j=3;j6;j+) if(positionij=B_KING)BlackLive=TRUE; if(positionij=R_KING)RedLive=TRUE; i=(m_nMaxDepth-nDepth+1)%2;/取当前奇偶标志,奇数层为电脑方,偶数层为用户方。/红方不在if(!RedLive)if(i)return 19990+nDepth; /奇数层返回极大值elsereturn -19990-nDepth;/偶数层返回极小值/黑方不在if(!BlackLive)if(i)return -19990-nDepth;/奇数层返回
28、极小值elsereturn 19990+nDepth; /偶数层返回极大值return 0;/将帅都在,返回04 界面设计和系统实现4.1 界面设计关于棋盘和棋子,建了一个基于对话框的MFC应用程序。主要工作都在对话框类的两个文件CChessDlg.h和CChessDlg.cpp下展开。代码主要分布于以下三大部分:1、初始化部分BOOL CCChessUIDlg:OnInitDialog()OnInitDialog()负责的是对话框的初始化。可以把有关中国象棋的棋局初始化情况也放在了这里面。初始化的内容包括:对引擎部分所用到的变量的初始化。包括对棋盘上的棋子位置进行初始化(棋盘数组的初始化),
29、对搜索深度、当前走棋方标志、棋局是否结束标志等的初始化;对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初始化;对程序辅助部分所用到的一些变量的初始化。包括对悔棋、还原队列的清空,棋盘、棋子样式的默认形式,下棋模式的默认选择,以及着法名称列表的初始化等。2、绘图部分void CCChessUIDlg:OnPaint() OnPaint()函数负责的是程序界面的绘图。因此,在这里将要完成棋盘、棋子的显示走棋起始位置和目标位置的提示框的显示。由于棋盘、棋子等都是以位图的形式给出的。所以在OnPaint()函数里做的工作主要都是在贴位图。需要注意的是由于位图文件不能像GIF文件那样有透明的背景并且棋子是圆形的而位图文件只能是矩形的,所以如果直接贴图的话会在棋盘上留下一块白色的边框棋子的背景。因此,要想让棋子文件的背景“隐藏”需要通过一些“与”和“异或”操作来屏蔽掉棋子的背景。3、走棋部分(用户动作响应部分)为WM_LBUTTONDOWN消息添加消息响应事件,可得到如下函数:void CCChessUIDlg:OnLButtonDown(UINT nFlags, CPoint point)当用户在窗口客户区按下鼠标左键时,程序就会调用OnLButtonDown(UINT nFlags, CPoint point)函数来进行响应。其中第二个参数CPoin
版权声明:以上文章中所选用的图片及文字来源于网络以及用户投稿,由于未联系到知识产权人或未发现有关知识产权的登记,如有知识产权人并不愿意我们使用,如有侵权请立即联系:2622162128@qq.com ,我们立即下架或删除。
Copyright© 2022-2024 www.wodocx.com ,All Rights Reserved |陕ICP备19002583号-1
陕公网安备 61072602000132号 违法和不良信息举报:0916-4228922