首页 中国象棋游戏的设计与实现毕业设计毕业论文

中国象棋游戏的设计与实现毕业设计毕业论文

举报
开通vip

中国象棋游戏的设计与实现毕业设计毕业论文中国象棋游戏的设计与实现 摘  要 象棋程序的实现可以被分为人工智能和界面程序辅助两大部分。人工智能部分主要体现计算机的下棋思路,既计算机如何进行思考并以最佳走法完成下一步,先由相应的搜索算法进行搜索,并对各种可能的走法进行估值,从中选择胜利面最大的一步;而界面及程序辅助部分主要便于用户通过以前的下棋步骤,更好地调整下棋思路,着法显示使用户能够清楚地知道下棋过程,更准确地把握整个局面。 本文首先研究了中国象棋在计算机中的表示问题,接着讨论如何产生着法一系列相关内容。其次研究了博弈树的极小极大搜索技术及在此基础上发...

中国象棋游戏的设计与实现毕业设计毕业论文
中国象棋游戏的 设计 领导形象设计圆作业设计ao工艺污水处理厂设计附属工程施工组织设计清扫机器人结构设计 与实现 摘  要 象棋程序的实现可以被分为人工智能和界面程序辅助两大部分。人工智能部分主要体现计算机的下棋思路,既计算机如何进行思考并以最佳走法完成下一步,先由相应的搜索算法进行搜索,并对各种可能的走法进行估值,从中选择胜利面最大的一步;而界面及程序辅助部分主要便于用户通过以前的下棋步骤,更好地调整下棋思路,着法显示使用户能够清楚地知道下棋过程,更准确地把握整个局面。 本文首先研究了中国象棋在计算机中的表示问题,接着讨论如何产生着法一系列相关 内容 财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容 。其次研究了博弈树的极小极大搜索技术及在此基础上发展起来的Alpha-Beta剪枝算法,使用MFC文档视图体系结构和Visual C++开发工具,实现了一个具有一定棋力的中国象棋人机对弈程序。 关键词:中国象棋;人工智能;博弈树;Alpha-Beta搜索 The Design and Implementation of Chinese Chess Abstract The implementation of a chess program can be decomposed into two major parts: the artificial intelligence and the user interface and program assist. The part of artificial intelligence shows the way of computer thinking, and which step is the best step would be decided by it. Firstly, the computer uses search algorithms to search, and then evaluates every impossible step, finally choses the best one, the other part is used for the player to adjust his thought to the currently phases. The display of step list makes player know the process of chess distinctly, and let player make a better choice. This paper firstly studies how to represent a chess board in computer, then discusses how to generate legal moves. Secondly, this paper studies the mini-max searching procedure of Game Tree, and the Alpha-Beta pruning algorithm. A Chess-playing system is designed and developed, which is built on the integrated computer MFC SDI document view architecture by using Visual C++. Key words: Chinese chess; Artificial Intelligence; Game tree; Alpha-Beta searching 目  录 论文总页数:22页 1    引言    1 1.1    象棋设计背景和研究意义    1 1.2    象棋设计研究方法    1 2    人工智能算法设计    2 2.1    棋局表示    3 2.2    着法生成    4 2.3    搜索算法    5 2.4    历史启发及着法排序    9 2.5    局面评估    9 2.6    程序组装    11 3    界面及程序辅助设计    12 3.1    界面基本框架    12 3.2    多线程    13 3.3    着法名称显示    14 3.4    悔棋和还原    15 4    系统实现    16 结    论    19 参考文献    20 致    谢    21 声    明    22 1 引言 1.1 象棋设计背景和研究意义 电脑游戏行业经过二十年的发展,已经成为与影视、音乐等并驾齐驱的全球最重要的娱乐产业之一,其年销售额超过好莱坞的全年收入。游戏,作为一种娱乐活动。早期的人类社会由于生产力及科技的制约,只能进行一些户外的游戏。随着生产力的发展和科技进步,一种新的游戏方式——电子游戏也随之诞生。 当计算机发明以后,电子游戏又多了一个新的载体。电子游戏在整个计算机产业的带动下不断地创新、发展着。自从计算机发明,向各个领域发展,到成为我们现在每天工作和生活必不可少的一部分的这个过程中,电子游戏也逐步渗入我们每个人的娱乐活动中。而计算机已经普及的今天,对于可以用计算机进行程序编辑的人来说,开发属于自己的游戏,已经不再是梦想。事实上,个人计算机软件市场的大约80%销售份额是来自游戏软件。棋牌游戏属于休闲类游戏,相对于角色扮演类游戏和即时战略类游戏等其它游戏,具有上手快、游戏时间短的特点,更利于用户进行放松休闲,为人们所喜爱,特别是棋类游戏,方便、快捷、操作简单,在休闲娱乐中占主要位置。作为中华民族悠久文化的代表之一,中国象棋不仅源远流长,而且基础广泛,作为一项智力运动,中国象棋开始走向世界。 随着计算机处理速度的飞速提高,人们很早就提出了疑问:计算机是否会超越人类?世界国际象棋大师已被计算机打败,计算机已经超过了人类?而人工智能是综合性很强的一门边缘学科,它的中心任务是研究如何使计算机去做那些过去只能靠人的智力才能做的工作。因此,对游戏开发过程中的人工智能技术的研究自然也就成了业界的一个热门研究方向。 1.2 象棋设计研究方法 对于象棋来说,核心设计主要包括人工智能算法的以及整个游戏中界面及程序辅助部分的实现,主要用 Visual C++ 进行开发,里面的MFC类库,使游戏开发更加方便,并利用人工智能相关搜索算法实现人工智能的着法生成,从而完善整个游戏的功能。 本文的目标是实现一款有着一定下棋水平且交互友好的中国象棋人机对弈程序。 该程序功能包括: *人机对弈; *搜索深度设定; (电脑棋力选择) *悔棋、还原; *着法名称显示; 整个程序的实现可分为两大部分: 一、人工智能算法设计(计算机下棋引擎) 该部分实现了如何让计算机下中国象棋,其中涉及人机对弈的基本理论及思想,是该程序的核心部分,同时也是本项目研究的重点所在。 二、界面及程序辅助设计 光有下棋引擎尚不能满足人机交互的基本要求,因此还需要一个框架(界面)来作为引擎的载体,同时提供一些诸如悔棋,还原之类的附属功能(程序辅助)。 下面分别介绍各部分实现。由于界面及程序辅助部分涉及内容宽泛而又繁琐,因而本文只介绍其中重点部分。 2 人工智能算法设计 程序的基本框架: 从程序的结构上讲,大体上可以将引擎部分划分为四大块: 棋局表示; 着法生成; 搜索算法; 局面评估。 程序的大概的思想是: 首先使用一个数据结构来描述棋局信息,对某一特定的棋局信息由着法生成器生成当前下棋方所有合法的着法并依次存入着法队列。然后通过搜索算法来逐一读取着法并调用局面评估函数对该着法所产生的后继局面进行评估打分,从中选出一个最有可能导致走棋方取胜的着法。在搜索的过程中还可以采用一些辅助手段来提高搜索的效率。其过程如下所示(图1): 图 1 程序结构图 下面将分别介绍程序各个部分: 2.1 棋局表示 计算机下棋的前提是要让计算机读懂象棋。所谓读懂,即计算机应该能够清楚地了解到棋盘上的局面(棋盘上棋子的分布情况)以及下棋方所走的每一种着法。因而首先需要设计一套数据结构来表示棋盘上的局面以及着法。 对于棋盘局面的表示可采用传统而简单的“棋盘数组”。即用一个9*10的数组来存储棋盘上的信息,数组的每个元素存储棋盘上是否有棋子。这种表示方法简单易行(缺点是效率不是很高)。按此方法棋盘的初始情形如下所示: BYTE CChessBoard[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 }; 给所有棋子定义一个值: #define R_BEGIN    R_KING #define R_END      R_PAWN #define 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_CANON    11//红炮 #define R_BISHOP  12//红士 #define R_ELEPHANT 13//红相 #define R_PAWN    14//红兵 判断颜色: #define IsBlack(x) (x>=B_BEGIN && x<=B_END)//判断某个棋子是不 是黑色 #define IsRed(x)  (x>=R_BEGIN && x<=R_END)//判断某个棋子是不 是红色 对于着法的表示,直接借用棋盘数组的下标来记录着法的起点和目标点。至于是什么棋子在走,以及是否吃子、吃的是什么子,在着法结构中并不记录。这些信息由外部读取棋盘上起点、终点的数据获得。着法结构定义如下,其中还包含了对着法的历史得分的记录项,以供后面要讲到的“历史启发”所用。 typedef struct { short nChessID;  //表明是什么棋子 CHESSMANPOS From;//起始位置 CHESSMANPOS To;  //走到什么位置 int Score;      //走法的分数 }CHESSMOVE; 有了对棋盘局面和着法的表示之后,程序才能够完成以下操作: 1、 生成所有合法着法; 2、 执行着法、撤销着法; 3、 针对某一局面进行评估。 因而,棋局表示好比是整个程序(计算机下棋引擎部分)的地基,之后所有的操作都将建立在其基础上。 2.2 着法生成 程序需要让计算机在轮到它走子的时候能够执行一步它认为对它最有利的着法,那前提就是它要有诸多(也可能是唯一)可供选择的着法,提供所有候选着法的“清单”就是着法生成器所要完成的。之后用搜索函数来搜索“清单”,并用局面评估函数来逐一打分,最后就可以选择出“最佳着法”并执行了。 在着法生成器中,采用的基本思想就是遍历整个棋盘(一个接一个地查看棋盘上的每个位置点),当发现有当前下棋方的棋子时先判断它是何种类型的棋子,然后根据其棋子类型而相应地找出其所有合法着法并存入着法队列。 这里谈到的“合法着法”包括以下几点: 1、    各棋子按其行子规则行子。诸如马跳“日”字、象走“田”字、士在九宫内斜行等等(这里需要特别注意的是卒(兵)的行子规则会随其所在位置的不同而发生变化——过河后可以左右平移)。 2、    行子不能越出棋盘的界限。当然所有子都不能走到棋盘的外面,同时某些特定的子还有自己的行棋界限,如将、士不能出九宫,象不能过河。 3、    行子的半路上不能有其它子阻拦(除了炮需要隔一个子才能打子之外)以及行子的目的点不能有本方的棋子(当然不能自己吃自己了)。 4、    将帅不能碰面(本程序中只在生成计算机的着法时认为将帅碰面是非法的,而对用户所走的导致将帅碰面的着法并不认为其非法,而只是产生败局罢了)。 产生了着法后要将其存入着法队列以供搜索之用,由于搜索会搜索多层(即考虑双方你来我往好几步,这样才有利于对局面进行评估以尽可能避免“目光短浅”),所以在把着法存入着法队列的时候还要同时存储该着法所属的搜索层数。因此可以将着法队列定义为二维数组m_MoveList[8][80],其中第一个数组下标为层数,第二个数组下标为每一层的全部着法数。 关于搜索层数,设定为8,实际使用的是1到7(在界面中将其限定为1—7)。搜索层数的增加会显著提高电脑的下棋水平(当然计算机的棋力在很大程度上也依赖于局面评估)。在配置为1.5G,512M内存的计算机上最多只能搜索4层,再多将导致搜索时间达到令人无法容忍的地步(这里还需要特别说明的是,搜索的速度也和着法生成的效率以及局面评估的复杂度有关,因为每分析一个结点都要执行这两种操作)。 对于每一层的着法数,也就是当前下棋方针对当前局面的所有可选的合法着法,据有关数据统计在象棋实战中一般最多情况下也就五六十种。定义第二个数组下标为80,应当可以保证十分的安全。 着法生成为搜索部分提供了“原料”,接下来的任务就交给搜索和局面评估了。 2.3 搜索算法 搜索算法对于整个下棋引擎来说都是至关重要的。它如同程序的心脏,驱动着整个程序。搜索算法的好坏直接影响着程序执行的效率(从某种角度上,它影响着计算机的下棋水平。因为,计算机必须在有限的时间内完成思考,搜索速度快意味着在相同的时间内程序可以“看”得更远,“想”的更多)。关于棋类对弈程序中的搜索算法,已有成熟的Alpha-Beta搜索算法以及其它一些辅助增强算法(还有众多基于Alpha-Beta算法的派生、变种算法)在程序中直接借鉴了Alpha-Beta搜索算法并辅以了历史启发。本节先介绍Alpha-Beta搜索算法:在中国象棋里,双方棋手获得相同的棋盘信息。他们轮流走棋,目的就是将死对方,或者避免被将死。 由此,可以用一棵“博弈树”(图2)来表示下棋的过程——树中每一个结点代表棋盘上的一个局面,对每一个局面(结点)根据不同的走法又产生不同的局面(生出新的结点),如此不断直到再无可选择的走法,即到达叶子结点(棋局结束)。中国象棋的博弈树的模型大概如下图所示,可以把其中连接结点的线段看作是着法,不同的着法自然产生不同的局面。 图 2博弈树 该树包含三种类型的结点: 1、 奇数层的中间结点(以及根结点),表示轮到红方走棋; 2、 偶数层的中间结点,表示轮到黑方走棋; 3、 叶子结点,表示棋局结束。 现在让计算机来下中国象棋,它应当选择一步对它最有利的着法(最终导致它取胜的着法)。获得最佳着法的方法就是“试走”每一种可能的着法,比较它们所产生的不同后果,然后从中选出能够产生对自己最有利的局面的着法。 结合上面所讲的博弈树,若给每个结点都打一个分值来评价其对应的局面(这一任务由后面所讲的局面评估来完成),那么可以通过比较该分值的大小来判断局面的优劣。假定甲乙两方下棋,甲胜的局面是一个极大值(一个很大的正数),那么乙胜的局面就是一个极小值(极大值的负值),和棋的局面则是零值(或是接近零的值)。如此,当轮到甲走棋时他会尽可能地让局面上的分值大,相反轮到乙走棋时他会选尽可能地让局面上的分值小。反映到博弈树上,即如果假设奇数层表示轮到甲方走棋,偶数层表示轮到乙方走棋。那么由于甲方希望棋盘上的分值尽可能大,则在偶数层上会挑选分值最大的结点——偶数层的结点是甲走完一步棋之后的棋盘局面,反映了甲方对棋局形势的要求。同样道理,由于乙方希望棋盘上的分值尽可能小,那么在奇数层上会选择分值最小的结点。这是“最小-最大”(Minimax)的基本思想。这样搜索函数在估值函数的协助下可以通过在奇数层选择分值最大(最小)的结点,在偶数层选择分值最小(最大)的结点的方式来搜索以当前局面为根结点、限定搜索层数以内的整棵树来获得一个最佳的着法。然而不幸的是,博弈树相当庞大(它会成指数增长),因而搜索(限定层数以内的)整棵树是一件相当费时的工作——其时间复杂度为O(bn)。其中b是分枝因子,即针对各种局面的合法着法的数目的平均值,n是搜索的深度。对于中国象棋而言,在中盘时平均着法数目大约是40种左右,那么搜索4层需要检查250万条路线,搜索5层需要检查1亿条路线,搜索6层需要检查40亿条路线! Alpha-Beta搜索能在不影响搜索精度的前提下大幅减少工作量。 因为,如果考虑到下棋是一个你来我往的交替进行并且相互“较劲”的过程。由于每一方都会尽可能将局面导向对自己有利而对对方不利的方向(假定下棋双方对棋局有着同样的认知,即你认为对你很糟糕的局面,在你的对手看来则是对他很有利的局面),那么某些局面由于能够产生出很糟糕的局面因而根本没有再继续考虑的价值。所以当你看到某个局面有可能产生很糟糕的局面时(确切地说这里的“很糟糕”是与之前分析的情况相比较而言的),你应当立刻停止对其剩余子结点的分析——不要对它再抱任何幻想了,如果你选择了它,那么你必将得到那个很糟糕的局面,甚至可能更糟……这样一来便可以在很大程度上减少搜索的工作量,提高搜索效率,这称为“树的裁剪”。 下面用图来进一步说明“树的裁剪”。为了简便起见,将博弈树进行了简化——每个结点只有三个分支,实际情况中,刚才讲过在盘中应有大约40个分支。 假定棋盘上的局面发展到了结点A(图3),现在轮到你走棋了,你是“最大的一方”——即你希望棋局的分值尽可能的高。用搜索两层来看一看“树的裁剪”对提高搜索效率的帮助。 图中 表示该结点要取子结点中的最大值; 表示该结点要取子结点中的最小值。 图3 树的裁剪 首先,考察结点A的子结点B。结点B所属的这一层是轮到你的对手——“最小者”来走棋了,目的是使得棋局的分值尽可能的小。依次考察结点B的各个子结点,查看它们的分值(因为事先约定好了搜索两层,现在已达到搜索深度的要求了,所以就停下来调用局面评估函数来给它打分)。结点B的第一个子结点(从左到右算起)返回10,第二个子结点返回了-5,第三个子结点返回了2。由于结点B这层是你的对手来做选择,假设他一定会做出明智的选择(你不能寄希望于你的对手会走出一步“昏招”),那么他会选择返回值为-5的那个结点。-5最终也就成了从结点B传递回的值,即倘若你(现在位于结点A)选择了产生结点B的走法,使得局面发展到了结点B。那么下一步,你的对手的选择就会使得棋局发展成为分值为-5的那个结点所表示的局面。 再来分析结点A的第二个子结点C,结点C与结点B同属一层,它依然是轮到你的对手作选择。依次查看结点C的各个子结点的分值,其第一个子结点返回了-8…… 采用 “裁剪”方法。不必再继续考察结点C的剩余子结点了,因为结点C已经够糟糕的了,不管结点C的剩余子结点有怎样的分值,它最多只能传回-8(有可能其剩余子结点中还有分值更小的结点,因而结点C还有可能传回更小的值)。而与前面已经分析过的结点B所传回-5相比较,作为“最大一方”的你显然更不愿意看到-8的局面。所以,你当然不会选择相应的着法使得局面发展成为结点C。因为那样的话,下一步你的对手就会带给你一个分值不高于-8的局面。 由此,在不影响搜索质量的前提下避免了搜索“无价值的”结点C的剩余子结点的大量工作,从而节省了宝贵时间,为在同样机器配置下搜索更多的层数提供了可能。 “最小-最大”的思想再加上“对树的裁剪”,这就是Alpha-Beta搜索算法的核心。最基本的Alpha-Beta算法的代码如下: int AlphaBeta(int depth, int alpha, int beta) { if (depth == 0)            //如果是叶子节点(到达搜索深度要求) return Evaluate();    //则由局面评估函数返回估值    GenerateLegalMoves();    //产生所有合法着法 while (MovesLeft())        //遍历所有着法 { MakeNextMove();        //执行着法 int val = -AlphaBeta(depth - 1, -beta, -alpha); //递归调用            UnmakeMove();        //撤销着法 if (val >= beta)        //裁剪 return beta; if (val > alpha)        //保留最大值 alpha = val; } return alpha; } 2.4 历史启发及着法排序 既然Alpha-Beta搜索算法是在“最小-最大”的基础上引入“树的裁剪”的思想以期提高效率,那么它的效率将在很大程度上取决于树的结构——如果搜索了没多久就发现可以进行“裁剪”了,那么需要分析的工作量将大大减少,效率自然也就大大提高;而如果直至分析了所有的可能性之后才能做出“裁剪”操作,那此时“裁剪”也已经失去了它原有的价值(因为你已经分析了所有情况,这时的Alpha-Beta搜索已和“最小-最大”搜索别无二致了)。因而,要想保证Alpha-Beta搜索算法的效率就需要调整树的结构,即调整待搜索的结点的顺序,使得“裁剪”可以尽可能早地发生。 可以根据部分已经搜索过的结果来调整将要搜索的结点的顺序。因为,通常当一个局面经过搜索被认为较好时,其子结点中往往有一些与它相似的局面(如个别无关紧要的棋子位置有所不同)也是较好的。由J.Schaeffer所提出的“历史启发”(History Heuristic)就是建立在这样一种观点之上的。在搜索的过程中,每当发现一个好的走法,就给该走法累加一个增量以记录其“历史得分”,一个多次被搜索并认为是好的走法的“历史得分”就会较高。对于即将搜索的结点,按照“历史得分”的高低对它们进行排序,保证较好的走法(“历史得分”高的走法)排在前面,这样Alpha-Beta搜索就可以尽可能早地进行“裁剪”,从而保证了搜索的效率。 对于着法的排序可以使用各种排序算法,在程序中采用了归并排序。归并排序的空间复杂度为O(n),时间复杂度为O(nlog2n),具有较高的效率。 2.5 局面评估 前文已经讲过了棋局表示、着法生成、搜索算法(包括搜索辅助), 在象棋程序中如果说搜索算法是心脏,那么局面评估就是大脑。搜索算法负责驱动整个程序,而局面评估则负责对搜索的内容进行判断和评价。因而搜索与局面评估是整个下棋引擎的核心。 首先,先介绍一下在局面评估中需要考虑的因素。就不同的棋类可能要考虑的因素略有差异。在中国象棋中所要考虑的最基本的几个因素包括如下四点: 1、子力总和 子力是指某一棋子本身所具有的价值。通俗地讲就是一个棋子它值个什么价。例如,车值500的话,那可能马值300,卒值80等等。所以在评估局面时,首先要考虑双方的子力总和的对比。比如红方拥有士象全加车马炮,而黑方只有残士象加双马,则红方明显占优。 2、棋子位置 棋子位置,或称控制区域,是指某一方的棋子在棋盘上所占据(控制)的位置。例如,沉底炮、过河卒、以及车占士角等都是较好的棋子位置状态,而窝心马、将离开底线等则属较差的棋子位置状态。 3、棋子的机动性 棋子的机动性指棋子的灵活度(可移动性)。例如,起始位置的车机动性较差,所以下棋讲究早出车。同样四面被憋马腿的死马机动性也较差(对于一步也不能走的棋子,可以认为其机动性为零)。 4、棋子的相互关系 这一点的分析较为复杂,因为一个棋子与其它子之间往往存在多重关系。如:一个马可能在对方的炮的攻击之下同时它又攻击着对方的车。 在程序中,估值函数最后返回的是每一方的总分的差值,而各方的总分就是上面所提到的四个因素的打分的总和。 对于子力打分和控制区域打分,只要遍历棋盘,当遇到棋子时简单地去查事先定义好的“子力价值表”和“控制区域价值表”,取出相对应的值进行累加即可(这些值的具体设定参考了前人的程序并作了适当的调整,今后仍应根据电脑下棋所反映出的实际问题对这些值作适当修改)。 对于机动性打分,需要求出各个子总共有多少种走法,然后根据各个子所不同的机动性价值每多一种走法就加一次相应的分数。      对棋子间相互关系的打分,要用到以下几个数据: int m_BaseValue[15];        //存放棋子基本价值 int m_FlexValue[15];        //存放棋子灵活性分值 short m_AttackPos[10][9];    //存放每一位置被威胁的信息 BYTE m_GuardPos[10][9];      //存放每一位置被保护的信息 BYTE m_FlexibilityPos[10][9];//存放每一位置上棋子的灵活性分值 int m_chessValue[10][9];    //存放每一位置上棋子的总价值 其中计算机会进行所有棋子值的判断,AttackPos和GuardPos分别记录该棋子受到的威胁和被保护的值。 当遍历一遍棋盘之后,子力打分、控制区域打分和机动性打分都可以完成,而关系表也可以填完。之后,再根据关系表来具体考察棋子的相互关系,进行关系打分。 分析关系时,首先,对王的攻击保护应分离出来单独考虑,因为对王的保护没有任何意义,一旦王被吃掉整个游戏就结束了。 其次,对一个普通子,当它既受到攻击又受到保护的时候要注意如下几个问题: 1、攻击者子力小于被攻击者子力,攻击方将愿意换子。比如,一个车正遭受一个炮的攻击,那么任何对车的保护都将失去意义——对方肯定乐意用一个炮来换一个车。 2、多攻击\单保护的情况,并且攻击者最小子力小于被攻击者子力与保护者子力之和,则攻击方可能以一子换两子。 3、三攻击\两保护的情况,并且攻击者子力较小的二者之和小于被攻击者子力与保护者子力之和,则攻击方可能以两子换三子。 4、攻击方与保护方数量相同,并且攻击者子力小于被攻击者子力与保护者子力之和再减去保护者中最大子力,则攻击方可能以n子换n子。 当然,上述四条只是覆盖了最常见的几种情况,覆盖并不全面。而且,在程序中并没有直接地重新考虑双方兑子之后的控制区域及机动性变化情况(之所以说没有“直接地重新考虑”,是因为搜索继续展开结点后仍会考虑这些因素,只是目前不知这样效果是否受影响——考察这两种方法在效果上的差异需要一定数量的试验数据的支持)。所以,如果今后要对引擎进行改进,提高程序的下棋水平的话,还应当在此进行研究。 2.6 程序组装 至此,已具备了实现一款中国象棋对弈程序引擎部分的所有要素,将上述模块分别写作.h头文件。如下: Define.h ——象棋相关定义。包括棋盘局面和着法的表示。 CMoveGenerator.h ——着法生成器。就当前局面生成某一方所有合法着法。 CSearchEngine.h ——搜索部分。使用搜索求出最佳着法。 HistoryHeuristic.h ——历史启发。Alpha-Beta搜索之补充,以提高搜索效率。 ——着法排序。对着法按其历史得分进行降序排序,以提高搜索效率。 CEveluate.h ——局面评估。为某一特定局面进行评分。 当实现了引擎部分的各要素时,可先建了一个Win32控制台项目,之后只要再添加一个.cpp文件负责接受用户的输入、调用搜索函数、显示搜索结果,便可简单的测试引擎了(采用输入着法的起点坐标和终点坐标的方式来传送用户走棋的信息。同样,程序显示计算机走棋的起点坐标和终点坐标来做出回应)。 此后,等到界面部分初步完成,引擎的上述各模块无需作任何改动,仍以.h头文件的形式加入界面 工程 路基工程安全技术交底工程项目施工成本控制工程量增项单年度零星工程技术标正投影法基本原理 ,只要由界面中的某个.cpp文件调用搜索函数即可。这种连接方式实现起来非常简单。 3 界面及程序辅助设计 3.1 界面基本框架 关于界面,建了一个基于对话框的MFC应用程序。主要工作都在对话框类的两个文件CChessDlg.h和CChessDlg.cpp下展开。 代码主要分布于以下三大部分: 一、初始化部分 BOOL CCChessUIDlg::OnInitDialog() { } OnInitDialog()负责的是对话框的初始化。可以把有关中国象棋的棋局初始化情况也放在了这里面。初始化的内容包括: 对引擎部分所用到的变量的初始化。包括对棋盘上的棋子位置进行初始化(棋盘数组的初始化),对搜索深度、当前走棋方标志、棋局是否结束标志等的初始化; 对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初始化; 对程序辅助部分所用到的一些变量的初始化。包括对悔棋、还原队列的清空,棋盘、棋子样式的默认形式,下棋模式的默认选择,以及着法名称列表的初始化等。 二、绘图部分 void CCChessUIDlg::OnPaint() { …… } OnPaint()函数负责的是程序界面的绘图。因此,在这里将要完成棋盘、棋子的显示走棋起始位置和目标位置的提示框的显示。 由于棋盘、棋子等都是以位图的形式给出的。所以在OnPaint()函数里做的工作主要都是在贴位图。 需要注意的是由于位图文件不能像GIF文件那样有透明的背景并且棋子是圆形的而位图文件只能是矩形的,所以如果直接贴图的话会在棋盘上留下一块白色的边框——棋子的背景。因此,要想让棋子文件的背景“隐藏”需要通过一些“与”和“异或”操作来屏蔽掉棋子的背景。 三、走棋部分(用户动作响应部分) 为WM_LBUTTONDOWN消息添加消息响应事件,可得到如下函数: void CCChessUIDlg::OnLButtonDown(UINT nFlags, CPoint point) { …… } 当用户在窗口客户区按下鼠标左键时,程序就会调用OnLButtonDown(UINT nFlags, CPoint point)函数来进行响应。其中第二个参数CPoint point是在本程序中所要用到的,它给出了当鼠标左键被按下时,鼠标指针的位置坐标。可以通过这一信息来得知用户的走法。 在OnLButtonDown函数里处理如下两种操作: 1、如果用户点击鼠标的位置落在己方的棋子上,表示用户选中了该棋子,下一步将移动该子进行走棋(也可能用户下一步将会选择己方另外的棋子,总之这一操作会记录下用户所选的将要走的棋子)。 2、如果之前用户已经选过了棋子,那么这一次的点击(如果不是另选本方的其它棋子的话)表达了用户的一次走棋过程。在收到用户传达的走棋信息后,可先判断该着法是否合法(是否符合中国象棋的游戏规则),如果合法,则执行之。紧接着调用引擎的搜索函数计算出计算机对用户着法的应着,然后执行该应着。 如此,在OnLButtonDown函数里,实现了人与机器的对弈(当然每走一步棋,也还需要绘图函数来显示棋盘局面的更新)。 以上三部分并非界面程序的全部,而仅仅是与程序密切相关的部分。此外还有其它部分对程序同样必不可少,但这些部分主要由MFC自动生成,无需人为改动,故在此不多做介绍。 3.2 多线程 如采用单线程方式,按照如下顺序编写代码: 用户走棋 —〉计算机思考并走棋 按这种方式编写的程序似乎毫无问题,程序运行一切正常。 然而,在给程序加入这些功能(后面将会在讲到其实现)后,程序出现了异常:有时对用户方的功能完全正确,而对电脑方的有些功能却不起作用!是由于程序在进行搜索时会占用大量的CPU时间,因而阻塞了位于同一线程内的其他指令,使之无法正常工作。解决 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 就是另外开一个线程,让各程序分开于多个线程。 启动一个新的线程的方法非常简单,只需调用函数AfxBeginThread即可,函数原型: CWinThread* AfxBeginThread( AFX_THREADPROC ThinkProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); 该函数启动一个新的线程并返回一个指向该新线程对象的指针,然后新的线程与启动该新线程的线程同时运行。该函数的第一个参数AFX_THREADPROC ThinkProc指定了线程函数。线程函数的内容即为新线程所要执行的内容,线程函数执行完毕,新线程结束(自动销毁)。 线程函数必须被定义为全局函数,其返回值类型必须是UINT,必须有一个LPVOID类型的参数。可以把调用引擎部分的搜索函数的代码以及完成走棋动作的代码放入所定义的思考线程内,如下: DWORD WINAPI ThinkProc(LPVOID pParam) { CChessDlg* pDlg=(CChessDlg*)pParam; pDlg->Think();  //计算机思考并走棋 return 0; } 然后,只要将原先调搜索函数并完成走棋的代码代之以调用AfxBeginThread来启动新线程即可,实现了程序的多线程,不能正常工作的问题也就随之解决了。 3.3 着法名称显示 每当下棋方(用户或是计算机)走一步棋,在棋盘旁边的一个列表框控件(List Box)中按照中国象棋关于着法描述的 规范 编程规范下载gsp规范下载钢格栅规范下载警徽规范下载建设厅规范下载 要求显示出该着法的名称。如:红炮二平五、黑马8进7此类。为了获得该着法名称,写了一个六百余行的函数。其功能就是将被移动的棋子类型以及走法的起点坐标、终点坐标这些信息转换成中国象棋所规范的着法名称。由于该函数主要涉及的是中国象棋关于着法表示的规范要求,故在此不对其具体实现做额外的解释。主要讨论的是如何对列表框控件(List Box)进行操作,以显示或删除着法名称。 首先,在ChessDlg下定义以下函数: this->GetMoveStr(nFromX,nFromY,nToX,nToY,nSourceID); //用来获得刚下的一步棋的走法; void CChessDlg::AddChessRecord(int nFromX,int nFromY,int nToX,int nToY,int nUserChessColor,int nSourceID) //将走法添加进下棋记录; 然后,显示在Listbox中。 当列表框中的项的数目超过列表框的显示范围时,列表框会自动添加垂直滚动条(前提是其VerticalScrollbar属性要为True——该属性默认即为True)。但是显示的内容依然是最早加进来的项。在控件属性里选择Vertical  Scroll,使得列表框可垂直滚动以显示最新的着法名称。 想要从列表框中删除项时,可以使用 m_lstChessRecord.DeleteString(m_lstChessRecord.GetCount()-1);减一之后正好是最后一项的行号。 3.4 悔棋和还原 悔棋和还原是棋类软件中较为基本的功能。要实现悔棋和还原功能,首先要明确哪些信息应当被保存以供悔棋和还原所使用。 在程序中保存了如下信息: 棋局表示中所定义的棋盘数组; 各棋子的贴图位置。 这里需要特别说明的是通常象棋程序处于程序效率的考虑并不保存所有棋子的信息,而只是保存之前一步的走棋信息。此后当悔棋的时候,需要撤销着法;还原的时候,需要执行着法。然而,在编写自己的程序时一来考虑到程序的可读性和不易出错性,二来考虑到对当今的计算机的配置来说这点开销基本上不会对程序的效率产生什么影响。因此保存了全部棋子的信息。 根据所要保存的数据定义了如下基本结构类型: typedef struct { CHESSMOVE cmChessMove; short nChessID;//被吃掉的棋子 }UNDOMOVE; 在对弈过程中,每一回合都将棋局信息(这里指前面所说的需要保存的信息)保存至走法队列,以供悔棋所用。还原功能是与悔棋功能相对应的,只有当产生了悔棋功能之后,还原功能才会被激活。一个回合的结束意味着前一次操作没有悔棋功能的产生,因此还原队列也应被清空。 在悔棋中主要完成了以下任务: 1.下棋回合数减一; 2.将当前局面信息保存至走法队列,以供还原所用; 3.从走法队列中取出上一回合的棋局信息,恢复到当前局面,然后将其从 队列中剔除掉; 4.将显示着法名称的列表框中的本回合的着法名称保存到一个着法名称队 列,以供还原所用。然后从列表框中删除它。 而在还原中所做的刚好和悔棋相反: 1.下棋回合数加一; 2.将当前局面信息保存至队列,以供悔棋所用; 3.从队列中取出最近一次悔棋前的棋局信息,恢复到当前局面,然后将其从队列中剔除; 4.从走法队列中取出最近一次存入的着法名称(两项,因为每回合会产生两步着法),将其重新显示到列表框中。然后将其从中剔除。 以上便是悔棋和还原功能所完成的具体操作,其代码分别写入悔棋和还原按钮(Button)的事件处理函数中。 4 系统实现 首先,执行该软件,系统并不需要很高的配置,CPU在1.0G以上,内存在512M以上就可以很流畅地执行。下面简单介绍一下象棋相关规则: 对局时,由执红棋的一方先走,双方轮流各走一着,直至分出胜、负、和,对局即终了。轮到走棋的一方,将某个棋子从一个交叉点走到另一个交叉点,或者吃掉对方的棋子而占领其交叉点,都算走一着。双方各走一着,称为一个回合。如果有一方的主帅被对方吃了,就算那一方输。 各种棋子的走法: *帅(将):帅和将是棋中的首脑,是双方竭力争夺的目标。它只能在‘九宫‘之内活动,可上可下,可左可右,每次走动只能按竖线或横线走动一格。帅与将不能在同一直线上直接对面,否则走方判负。 *仕(士):仕(士)是帅(将)的贴身保镖,它也只能在九宫内走动。它的行棋路径只能是九宫内的斜线。 *相(象):相(象)的主要作用是防守,保护自己的帅(将)。它的走法是每次循对角线走两格,俗称’象走田‘。相(象)的活动范围限于’河界‘以内的本方阵地,不能过河,且如果它走的’田‘字中央有一个棋子,就不能走,俗称’塞象眼‘。 *车:车在象棋中威力最大,无论横线、竖线均可行走,只要无子阻拦,步数不受限制。因此,一车可以控制十七个点,故有’一车十子寒‘之称。 *炮:炮在不吃子的时候,走动与车完全相同。 *马:马走动的方法是一直一斜,即先横着或直着走一格,然后再斜着走一个对角线,俗称’马走日‘。马一次可走的选择点可以达到四周的八个点,故有’八面威风‘之说。如果在要去的方向有别的棋子挡住,马就无法走过去,俗称’蹩马腿‘。 *兵(卒):兵(卒)在未过河前,只能向前一步步走,过河以后,除不能后退外,允许左右移动,但也只能一次一步。 在懂的以上规则之后并可进行游戏,执行该软件后,并可进入游戏界面。棋盘界面(图4)所示: 图4 棋盘界面 从界面上方的菜单栏中可以进行相关设置 参数设置界面(图5)如下: 图5 参数设置界面 等你将参数设置完毕之后,既可进入游戏。走法记录界面(图6)如下: 图6  走法记录界面 其他辅助功能界面(图7)如下: 图7 其他辅助功能界面 “IT开拓者3网络工作室”成立于2010年,是一个专业的计算机软件开发团队。 “资源共享,信息互通” 需要更多相关设计资料和源代码加QQ:493703123
本文档为【中国象棋游戏的设计与实现毕业设计毕业论文】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_882336
暂无简介~
格式:doc
大小:71KB
软件:Word
页数:0
分类:工学
上传时间:2019-08-30
浏览量:23