中国象棋人机对弈毕业论文

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

中国象棋人机对弈毕业论

目录
1.综述 (1)
1.1选题的意义 (1)
1.2国内外研究现状概述 (1)
1.3主要研究内容 (2)
2.数据结构 (4)
2.1棋盘的表示 (4)
2.2棋子的表示 (5)
3.棋子的走法 (7)
4.评估函数 (8)
5.搜索算法 (10)
5.1极大极小值搜索算法 (10)
5.2 alpha-beta剪枝算法 (12)
5.3 alpha-beta剪枝算法的改进 (13)
6.界面的实现 (15)
6.1棋盘区 (15)
6.2菜单项的设计 (16)
6.3常用按钮的设计 (17)
7.开局库 (18)
8.系统的实现 (19)
9.总结 (26)
参考文献 (27)
声明 (28)
致谢 ................................................................................................................................ 错误!未定义书签。

1.综述
1.1选题的意义
中国象棋在中国拥有悠久的历史,这个游戏需要两个人进行对弈。

由于中国象棋用具简单、趣味性强,成为流行极为广泛、老少皆宜的棋艺活动。

中国象棋是一种古老的文化,它集文化、科学、艺术、竞技于一体,有利于开发人的智慧,锻炼人的思维,培养人的毅力,增强人的竞争意识。

随着电脑技术及互联网的发展,人们下棋没有了地域限制,人们甚至可以跟电脑对战,于是就产生了人是否能够战胜电脑的疑问。

从很早开始,人们就开始进行棋类博弈的游戏了,而在人工智能领域,机器博弈始终是一个重要的组成部分。

人们对人工智能的窥探是从棋类博弈游戏开始的,人们在博弈游戏中,对战双方通过对游戏规则的掌握、丰富的经验和知识,使游戏的局面有利于自己,这就是人类的思维过程,于是棋类博弈就成了人工智能的实验品。

对机器博弈的研究取得的成果不仅仅只用在棋类游戏上,而且也已广泛应用于军事、政治、经济等多个领域,给人类带来了极大的社会效益。

1.2国内外研究现状概述
机器棋类博弈的研究最早是从国际象棋开始的,1950年美国著名数学家香农积几十年的研究,找到了编制国际象棋程序的原则方法。

他提出以数的函数评价局面的优劣。

函数的主题是通常一般实力的棋手都能考虑到的一些因素,诸如:棋子实力重叠兵孤立兵、落后兵的弱点以及车的通路和其他子力的活动性等等。

香农还提出用简化的估计方法剔除次要的变化。

他是计算机国际象棋理论的奠基人。

在数学家和计算机专家的共同努力下,20世纪50年代末终于试制出世界上第 1台公开与棋手对弈的电子计算机。

1974年,在瑞典的斯德哥尔摩举行了计算机国际象棋的第1届世界冠军赛,8个国家的13种弈棋程序按积分循环制进行比赛,结果苏联的“卡伊赛”程序获得冠军。

最出名的是1997 年,卡内基梅隆大学的“深蓝”小组研究开发出“更深的蓝”,挑战人类大师。

最后在全世界目光的关注下,“超级深蓝”击败了棋王卡斯帕罗夫。

成为人工智能历史上里程碑式的事件,也标志着机器博弈的重大成功。

和国际象棋相比,中国象棋机器博弈起步比较晚,八十年代才开始。

1981年张耀腾发表的《人造智慧在电脑象棋上的应用》,是第一篇研究中国象棋机器博弈的文章。

他在他的毕业论文中以残局做实验,提出审局函数为静态子力值,棋子位置值,棋子灵活度值,
威胁与保护等四项之和。

1982年廖嘉成发表的《利用计算机象棋的实验》就进了一步,包括开局、中局、残局。

台湾大学的许舜钦教授被称为中国计算机象棋之父。

在他1991年的两篇论文中,总结并介绍了到当时为止几乎所有的搜索算法,他在文中详细阐述了许多算法的不足之处并且解释了人们对这些算法的误解。

这些研究成果为以后计算机象棋的发展做好了铺垫,至今仍在指导着人们进行计算机象棋的研究和实验工作。

到了九十年代,中国象棋计算机博弈开始发展起来,人们研究出了各种博弈软件。

比较有代表性的有台湾的吴身润的《中国象棋》、光谱公司出品的《将族Ⅲ》、晟业编制的《象棋水浒战》等等。

1.3主要研究内容
文章主要是研究中国象棋的人机对弈,包括象棋的界面和引擎部分。

界面主要是方便人与电脑进行交互的可视化界面。

界面包括棋盘区、菜单项和功能按钮区。

主要实现棋子的移动、悔棋、记录棋谱、难度选择等选项功能。

引擎部分主要包括,棋子棋盘的表示即数据结构,走法的生成,局面优劣的评估即评估函数,搜索算法及其优化和改进。

主界面分为三部分:菜单栏,棋盘区,常用按钮。

菜单栏在最上面,有四个主菜单:游戏,难度,让子,棋谱。

游戏菜单包括“我先走”、“电脑先走”、“音效”、“退出”。

不论选择我先走还是电脑先走,棋盘区的棋子都会回到初始位置。

选择音效会有声音效果,取消音效为静音。

难度菜单包括“傻瓜”、“菜鸟”、“新手”、“入门”、“业余”、“专业”、“大师”,总共设有七个难度,选择任何一个难度不会影响棋盘区的布局,只会改变当前电脑的策略。

让子菜单包括“让单马”、“让双马”、“让九子”、“被让单马”、“被让双马”、“被让九子”,选择任何一个选项也会让棋盘区的棋子回到让子的初始状态。

棋谱菜单包括保存棋谱和读取棋谱,保存棋谱是保存从初始状态到当前局面的过程。

读取棋谱,读取出保存的棋谱并使棋盘区的棋子回到初始状态,棋盘下方的常用按钮替代为“上一步”“下一步”,可以通过这两个按钮演示棋局。

棋盘区是显示棋盘的区域,占据了整个界面的正中。

常用按钮区在棋盘区的下面,有三个按钮:悔棋、保存、复盘,悔棋是当人该下子的时候回到上一步,保存是保存当前棋盘的格局,复盘是恢复保存的棋盘格局,人可以接着回复的格局继续对弈。

文章设计是采用MFC的框架来实现界面部分,主要用到的MFC类有,CWnd:窗口,它是大多数“看得见的东西”的父类(Windows里几乎所有看得见的东西都是一个窗口,大窗口里有许多小窗口)。

CDC设备文本。

无论是显示器还是打印机,都是画图给用户看。

这图就抽象为CDC。

CDC与其他GDI(图形设备接口)一起,完成文字和图形、图像的显示工作。

CDialog对话框类,CPen类,CBrush类等等。

象棋对弈其实是一种零和博弈。

双人对弈,轮流走步;信息完备,双方得到的信息都是一样的;零和,即对一方有利的棋,对另一方肯定是不利的,不存在对双方均有利或无利的棋。

所以内核代码的思路要用到博弈算法。

对每一个局面所有可能的着法进行考虑,每一步着法会形成新的局面,这样就形成了一颗博弈树。

机器博弈就是要从这颗博弈树中找到最优的分支,即要用到搜索算法对整棵树进行搜索。

而中国象棋的博弈树是一个庞大的集合,如果一毫微秒走一步,也要需要10的一百多次方年才能搜索出必赢的第一步,所以博弈树是不可能穷举的。

人在思索下棋的时候也不可能对棋局所有的可能计算完全,人往往是凭自己的经验、知识对棋面的好坏进行评估。

通过这种思路我们可以给每个局面打一个分数来表示棋局对自己的有利程度。

如果轮到自己落子的时候,一定会选择使局面分数最高的着法,如果轮到对手落子,他一定会选择使你得分最低的局面。

这就是我们经常听到的极大极小值搜索,而对局面进行估分的函数就是评估函数。

极大极小值搜索就是在轮到自己落子的时候选择分数最大的局面,轮到对手落子的时候选择得分最小的局面,这样就使得搜索可以进行,而由于估分的存在我们就可以在搜索进行到可以接受的时间范围内时结束它。

2.数据结构
2.1棋盘的表示
任何程序都是由数据结构和算法构成的。

对中国象棋的计算机游戏编程,首先是要思考用什么样的数据结构来记录棋盘和棋子。

下图是一个中国象棋的棋盘:
图2-1 中国象棋棋盘
可以明显地看出,中国象棋的棋盘是由10*9根横竖直线相交的交叉点构成的。

那么我们很容易地想到了用一个10*9的数组board[10][9]来表示棋盘,棋盘的定义如下:
int board[10][9]={
1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,0,
0,1,0,0,0,0,0,1,0,
1,0,1,0,1,0,1,0,1,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
1,0,1,0,1,0,1,0,1,
0,1,0,0,0,0,0,1,0,
0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1
};
在这里我们用0表示某个位置上没有棋子,用1来表示某个位置上有棋子,棋子的具体表示方法我们在下一小节将会讨论。

我们还可以用一个一维数组来表示这个棋盘,棋盘从左到右再从上到下我们可以用一个一维数组board[90]来表示棋盘,一维数组的定义为:
intboard[90]={
1,1,1,1,1,1,1,1,1,
0,0,0,0,0,0,0,0,0,
0,1,0,0,0,0,0,1,0,
1,0,1,0,1,0,1,0,1,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
1,0,1,0,1,0,1,0,1,
0,1,0,0,0,0,0,1,0,
0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1
};
也就是说数组的第一个位置代表左上角的车,而数组的最后一个位置代表的事右下角的车。

用一维数组来表示棋盘有什么好处呢,首先一维数组比二维数组少了一个维度,可以用一个下标就能表示一个点,这对于找寻一个点的位置是十分方便的,其次只需通过下标的求余和求整计算就可以轻易得到某个点的横纵坐标。

还有一种更高效的办法,就是用一个256长度的一维数组来表示棋盘,它主要是利用了与运算和右移运算来实现求横纵坐标的,我们这里不再进行阐述。

2.2棋子的表示
上一小节为了阐述方便,我们简单地把有棋子标记为1,在实际的操作过程中,这样是远远不够的,因为我们知道不同的棋子作用是不同的,所以我们需要用新的不同的数字来代表不同的棋子。

我们可以按照棋盘上从左到右的顺序,用1来表示车、2表示马等等,可以总结为下
边的对应图:
车马象士将炮卒
车马相仕帅炮兵
1 2 3 4 5 6 7
这样我们就得出了一个简单的对应关系,我们通过数字的不同就可以判断棋子的不同种类,但是这样不能区别红黑双方的棋子,于是我们做一个小小的改进:车马相仕帅炮兵
1 2 3 4 5 6 7
车马象士将炮卒
8 9 10 11 12 13 14
那同一方的同类的棋子有没有差别呢?我们知道,如果马被封在了角落里它的作用可能还不及一个过河兵,如果马在卧槽的位置上那么它的威胁是十分大的,关于棋子的效力我们将在评估函数一节中讨论,所以即使是同一方相同的棋子也需要用不同的数字来表示,于是就形成了下边的对应:
车马相仕帅仕相马车炮炮兵兵兵兵兵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
车马象士帅士象马车炮炮卒卒卒卒卒
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
我们依然用0来表示棋盘上的位置上没有棋子。

当电脑在下棋的时候,它会挨着搜索棋盘上有哪些棋子,然后来判断棋子的移动,这样到了中后期,棋盘上的棋子很少的时候这样的做法效率是很低的。

于是我们就想到通过某一个棋子可以直接知道它是否还在棋盘上,如果在棋盘上能知道它在棋盘上的位置。

我们可以定义一个长度为32的一维数组来建立一个棋子到棋盘的映射,来记录棋子的信息。

如果我们用的是一维数组来表示棋盘,那么这个映射数组的值当为-1时我们可以知道棋子已经不再棋盘上了,我们可以用相应的整数来表示棋子在棋盘上的坐标。

3.棋子的走法
没有规矩不成方圆,任何棋类游戏都有它落子的规矩。

对于简单的五子棋来说,下棋双方轮流下棋,只要是棋盘上有空位的地方都可以落子。

相比于五子棋来说,象棋的规则就相对要复杂一些,像马走日、象走田、将不能出九宫格、士划斜线,还要考虑撇马脚、堵象眼等各种情况。

对于中国象棋电脑游戏,电脑对于人落子合理性的判断要相对简单一些,只需判断起始点的棋子的规则时候能够走到终点。

由于中国象棋的电脑思索过程是基于搜索的,所以电脑必须判断出棋子所有合法的落点。

根据棋子的走法,如车可以在横竖直线上走任意步,兵每次只能移动一步,这样我们可以得出棋子可能走到的点,我们还需要考虑棋子是否走出了棋盘,这种着法是不合理的。

我们还需要考虑规则的合理性,车在走任意步的时候起始点和终点之间没有棋子的阻隔,兵永远不能后退,兵在过河后才能左右移动,马脚时候被撇住,象的中心是否被堵住。

还要考虑当落子的终点上有对方的棋子话就形成了吃子。

另外,对于炮来说,炮的移动和吃子的规则是分开的,需要分开来考虑。

如果每次搜索都要一个棋子一个棋子地去考虑它们所有的落子位置,这样搜索的效率会大大降低。

通常我们会使用一种模板法的方法来简化棋子落子的选择,这种方法是利用空间去换取时间来提高效率。

模板法是将在某个位置上的棋子的所有可能的落点记录下来,例如某个位置上的棋子是一个马,根据马的行子规则我们可以记录下马所有可能达到的位置,存储起来作为模板,当我们搜索棋子与棋盘的映射数组时,如果我们知道了马正好在这个位置上,我们就可以直接得出马可能的几个落子位置,再检查马脚是否被撇,就可以很快地知道棋子的落点。

棋子走法的生成是在搜索的每层必须要用到的,能够简化走法生成的步骤缩短走法生成的时间将会大大提高搜索算法的时间,从而提高电脑下棋的速度。

4.评估函数
对于中国象棋来说,当一方的将帅被吃掉的时候棋局也就结束了,被吃掉的一方输掉了比赛,当双方都无论如何无法吃掉对方的将帅的时候就为和局。

电脑是通过搜索的办法来寻找下棋的策略的,如果电脑搜索到对方的将帅被吃的时候,那么返回的第一步就是电脑必赢的第一步,然而根据现在的硬件设备很多地方都证明了这需要上亿万年的时间,也就是说要需找出必赢的第一步棋几乎是不可能的,这样也使得人机对弈失去了意义。

于是我们就考虑怎样才能让搜索的时间控制在可以接受的范围内,假设我们只搜索到第N层,当到了第N层的时候我们就需要判断出哪一个局面对下棋的一方是最有利的。

对于人来说局面是否有利是凭自己的经验感觉、掌握的知识等来判断的,但是电脑它不能像人一样真正地思考,它需要我们给它制定一些规则来判断局面的好坏,这个告诉电脑棋局形势好坏的函数就是评估函数。

评估函数会以下棋的一方为主体给棋局进行打分,让电脑一目了然地知道哪个棋局对它有利,从而返回最佳的落子策略。

那怎么来判断一个局面的好坏呢,首先想到的就是棋子本身的价值。

通常我们会觉得车的价值比一个兵的价值大,这样我们就可以用一个分数来表示它们作用的大小。

有一个词叫弃车保帅,如果将帅都被吃掉了,那么游戏也就以失败告终了,所以将帅看似能力很小但我们要给它一个无穷的值来表示它的重要性,在计算机中我们用一个相对来说非常大的数字来表示将帅的值。

对于炮和马的价值差别是很模糊的,很少有人会在下象棋的第一步就用自己这一方的炮去换掉对方的马,当然这里面包含有转换先手的原因,而马的吃子的效果不用考虑到局面棋子的多少,相对来说比较灵活。

这里我们认为马的作用稍高于炮。

我们可以得出一个棋子与分数的简单对应:
车马象士将炮兵
700 320150 150 10000 300 100
这是一种最简单的判断局面好坏的方法,利用这种方法加上高效的搜索算法也可以写出一个具有相当棋力的象棋程序。

棋子除了它本身的价值还有它所处位置的影响,前面的内容我们就说到了如果一个被封在死角的马的作用不及一个卧槽马的作用,甚至不如一个在对方九宫格附近的过河兵的作用。

所以我们在考虑棋子本身价值的同时我们还需要考虑它的位置附加值。

我们可以做出所有棋子位置附加值的映射作为模板存储起来,给一个棋子在不同的位置打一个分数,当评估局面的好坏时,也把位置附加值加权在内。

像兵过河过后的附加值比没过河要大,兵进一步靠近对方的九宫格附加值更大,当兵被逼到对方的底线时附加值又会相应地减少。

对于将帅来说位于最起始的位置时附加值是最大的。

我们知道下围棋的时候最讲究的就是形和势,所谓的形就是棋子与棋子之间的相互作用。

像一些必杀的棋,炮与炮的组合双重炮,炮与马的组合马后炮,这些都是由自己一方
的棋子形成的强有力的杀招,当棋子的移动可以构成某些固定的形的时候,我们也应该给这样形加分。

除了这些杀招外,还有一些非常强硬的组合,像连环马、担子炮、霸王車,根据不同的形加上相应的附加值。

古文中有“夫六国与秦皆诸侯,其势弱于秦”,所谓的势就是保护与被威胁的情况。

当一方的某个棋子在下一步将会被对方吃掉的时候,如一方的马在对方的炮的吃子位置上,我们就认为被威胁的一方势要弱一些,就应该给主动威胁的一方加上相应的分数,而给被威胁的一方减去相应的分数。

我们还需要考虑到,在中国象棋中经常存在连环吃的情况,如果你吃了对方的一个子,对方马上也能吃掉你的那个子,也就是说某一方的一个棋子虽然在对方一个棋子吃子的威胁下,但是被威胁的那个棋子又在己方的某一个棋子的保护之下,这种局面处于动态稳定,我们不予加分。

如果对子的两颗棋子本身价值及位置附加值等相差甚远,我们又需要另外作考虑。

上面介绍了一些常用的判断局势的依据,依据这些依据可以写出十分强大的象棋程序,但是如果和顶尖棋手较量还有一定的差距。

就像完全展开博弈树一样,这是一个理想的状态。

对于评估函数,如果它能像人一样或者比人的思维更强,它能考虑到各种各样的情况,计算到各种各样的因素,换句话说它是一个完美的评估函数,那么我们就不再需要搜索算法。

我们只需要凭借评估函数就能知道要走的棋是哪一步。

但是这样的评估函数我们都知道至少现在是不可能的,但是人们也在朝着这个方向努力,神经网络评估函数就是想实现这个目标。

庞大的象棋程序会吸纳成千上万的优秀棋局作为计算机的参考,并且计算机还具有一定的学习修正能力。

这需要用到更深奥的知识,如现在的加强学习法,自适应遗传算法等。

5.搜索算法
5.1极大极小值搜索算法
中国象棋是一种零和博弈游戏,所谓零和即一方的收益必然意味着另一方的损失,博弈双方的收益和损失相加总和永远为零。

博弈双方轮流下棋,都选择对自己最有利即对对手最坏的情况,这样交替就形成了一颗博弈树。

为了阐述方便,我们假设后边都是一个叫Max的一方即将下棋,而他的对手叫做Min,而评估函数计算出的分数都是相对于Max的得分。

当Max下棋的时候他一定会选择对自己最有利的着法,即得分最高的局面,我们称为Max点,而轮到Min下棋的时候,他一定会选择对Max来说最坏的局面,即得分最低的局面,我们称为Min点。

这个轮流思考的过程我们用搜索算法表达出来就是极大极小值搜索算法。

图5.1 一颗博弈树
如图5.1是一颗分支为3,深度为2的博弈树。

正方形的点代表它是一个极大值点,它选择它的后继者中值最大的局面。

圆形的点代表它是一个极小值点,它选择它的后继者中值最小的局面。

例如点p(1),它是一个极小值点,在它的后继者中p(1,2)的值最小,所以它的值就为3。

对于根节点p来说,它是一个极大值点,它的后继者中p(1)的值最大,所以它的值就为3。

下边是极大极小值的一段伪代码。

intMaxmin(int depth)
{
If (0==depth)
return Evalute(); //返回评估函数的值
if (Max节点)
{
for (生成每一个可能的走法)
{
执行走法;
flag=Maxmin(depth-1);
撤销走法;
If (flag>value)
value=flag;
}
}
if (Min节点)
{
for (生成每一个可能的走法)
{
执行走法;
flag=Maxmin(depth-1);
撤销走法;
If (flag<value)
value=flag;
}
}
return value;
}
通过观察可以看出,极大值点和极小指点的处理有很多相似之处,产生了很多冗余的代码,为了使代码简洁方便管理,可以做一个小小的处理。

我们让Maxmin()函数每次都返回负值,然后在采用极大值点的比较方法flag>value来判断value是否赋值。

这样就可以大大地简化代码,伪代码如下:
intMaxmin(int depth)
{
If (0==depth)
return Evalute(); //返回评估函数的值
for (生成每一个可能的走法)
{
执行走法;
flag=-Maxmin(depth-1);
撤销走法;
if (flag>value)
value=flag;
}
}
这样做没有改变程序的效率,只是使得代码整洁易懂。

这种做法也叫做负极大值算法。

可能有人会问这样不是改变了返回值,程序的正确性怎样保证。

其实要做修正的不仅仅是这里,在评估函数中,当下棋的是Max点时返回负的估值,当下棋的是Min点时依旧返回原来的估值。

这样就保证了经过处理的返回值保持不变,这里还需要向评估函数传递一个参数,用来表明当前下棋的是谁。

5.2alpha-beta剪枝算法
上一小节中我们已经讨论过,将一颗博弈树完全展开几乎是不可能的,要找到必胜的第一步也是不可能的。

但是如果越靠近博弈树的末端我们可以认为越接近最终局面,也就是说我们希望尽可能的搜索更深的层次,但是搜索的深度和时间的花费是成正比的,我们必须提高搜索算法的效率才能节约更多的时间。

这样我们就会想到每次搜索的过程中是不是有很多其实不需要搜索的分支浪费了很多时间。

到底哪些分支不需要搜索了,哪些分支需要减掉呢,我们来看下边的分析。

图5.2
如图5.2,我们采用的是从上到下从左到右的深度优先搜索,p(1,1)的值v(1,1)=3,由于p(1)是一个极小值点,它的取值是它所有继承者中值最小的那个点,于是我们可以得到v(1)<=3。

再看点p(1,2),当我们搜索到它的第二个子节点时我们通过上述的方法可以得到v(1,2)>=5,也就是说点p(1,2)的值绝对不会比5小。

我们再看点p(1),它的值绝对不会比3大,所以它不会取它第二个子节点p(1,2)的值,那么p(1,2)剩余的子节点就不用搜索了。

这时就发生了alpha剪枝。

同样地,由于根节点p的值取的是它的子节点中最大的值,由于p(1)=3,那么可以得出p的值v>=3。

而p(2)的第一个子节点的值可以推出p(2)的值v(2)<=1,所以p (2)节点剩下的子节点都用搜索了,这样就发生了beta剪枝。

下边是融合了alpha-beta 剪枝算法的极大值极小值算法的伪代码:
IntMaxmin(intdepth,intalpha,int beta)
{
If (0==depth)
return Evalute(); //返回评估函数的值
for (生成每一个可能的走法)
{
执行走法;
flag=-Maxmin(depth-1,-beta,-alpha);
撤销走法;
if (flag>alpha)
alpha=flag;
if (alpha>=beta)
break;
}
Return alpha;
}
传递参数的时候之所以要调换beta和alpha的值,是因为要迎合负极大值算法。

应用alpha-beta算法可以省去很多冗余的分支,节省很多时间。

在alpha-beta剪枝算法中我们希望剪枝发生越早越好,这就和节点的顺序有关,事实上根据这个思想alpha-beta 剪枝算法可以进一步改进。

相应的改进方法有静态启发和迭代深化等。

5.3alpha-beta剪枝算法的改进
上面已经说过alpha-beta剪枝算法非常依赖于棋子走法的搜索顺序,如果每次运气都。

相关文档
最新文档