【五子棋AI循序渐进】——开局库
首先,对前面几篇当中未修复的BUG致歉,在使用代码时请万分小心…………尤其是前面关于VCF\VCT的一些代码和思考,有一些错误。虽然现在基本都修正了,但是我的程序还没有经过非常大量的对局,在这之前,不打算再发整体代码了。简要说一下现在的情况:
就棋力来说,不加开局库我也下不过它了,先别管它到底怎么样,至少说明它的水平比我的提高的多。不过记得有人有这样的论点:棋类AI的水平很大程度上决定于作者棋艺。应该主要指知识方面的代码吧。也不是很同意,个人觉得还是作者对AI工作原理和棋类特点的理解。
就使用的技术来说,主要就是这么几点:
一、局面
0、冲棋点搜索:取代历史表、利用1、2易于判定组合棋型
1、棋盘表示:可成五向量、4进制位棋盘
2、模板表示:从5长度到15长度的模板、并由代码计算全部棋型
3、冲棋值表:记录单独、复合棋型,实现“段排序技术(实现上无需使用排序代码提高效率)“取代历史表排序
4、双层置换表:即双置换表技术
5、局面分段评价:基于冲棋点搜索、”段排序技术“,可以只访问棋盘上空点,效率比较高。
6、招法生成器:这其中包括5种它们都是基于冲棋点搜索、“段排序技术”,只生成“必要”的走法,是提前剪枝的重要手段。
这5种生成器分别用于PVS,VCF进攻/防守,VCT进攻/防守的走法生成。
7、低冲棋值招法找回启发:弥补招法生成器提前剪枝对低冲棋值招法的忽略。这一技术在找回重要的低冲棋值招法时不产生额外计算,所以效率很高。
二、搜索(其实按下面的不是很科学,因为剪枝和VCT/VCF之间存在复杂的相互作用)
1、最底层引擎:
A、超出边界的α-β剪枝
B、置换表启发
C、内部迭代加深启发
D、迭代加深
这里没有使用冲棋延伸、静态搜索等技术。其原因主要是VCT/VCF的作用和速度问题。基于同样的原因,还没有给引擎增加做VCF/VCT的功能,所以现在的攻击性仅限于冲棋点搜索本身的浅深度攻击;而防守方面,仅限于浅层VCF/VCT搜索。这一切的一切都是VCF/VCT尤其是VCT函数做的孽……
2、中层引擎
A、无遗漏的全局VCF搜索
B、有遗漏的全局VCT搜索
这里使用独立的交叉递归函数和走法生成器,VCF进行全搜索,VCT进行部分搜索。即使进行部分搜索,VCT也非常慢,因为空间复杂度太高,后面打算使用类似于五子棋终结者的方式把搜索点按“段排序技术”所得到的段逐步扩大搜索,以期得到更好的效率。
3、开局库搜索
A、压缩存储:约200万局面只存储了1.3m的空间,对等局面采用7种旋转合为一个。
B、VCF/VCT发现:黑方达到可VCT/VCF时调用VCT/VCF搜索函数
C、最强防:白方查找最强防,以期脱谱获胜。(木有办法,地毯普是执黑必胜普,说白了就是延长黑方胜利步数等弱防或者错招从而正确脱谱)
下面才进入这次的主要问题,开局库(额,也许五子棋这么叫不是十分合适了,就沿用这个叫法吧),程序不准备带单独的开局库,因为一方面没有什么装配件就一EXE,更主要的就是现在地毯普网上很好找到全面地毯,而且都已经给搜索到可以VCF/VCT的程度了,根本没有自定义的必要。制作开局库的思路:
1、网上搜集比较全面的地毯普,合并,精简其对我们无用的各种说明,只留下棋盘文字信息并统一格式,一般有123,abc,ABC等表示,自己统一一下就行了。
2、将其保存,因为使用了renlib的文件格式保存,所以接下来的做法有2种,第一种可以另存为文本棋盘,然后解读,这个很好解读,但是需后期统计或人工加工招法好坏;第二种解析.lib文件格式。我使用的是第二种方式,因为在renju的网站上可以下载到这部分的源码(C语言的吧,额看起来还好,只是我打开编译不了……还有很多默认int类型不支持,我去~~~~~),现在应该是3.6的,很容易能够看出.lib文件的组织方式:第一字节是位置,它的横坐标是从1开始(应该是为了代码里面0坐标作为非法坐标,我的程序里是把&HFF作为非法坐标使用),第二字节是说明,在其const中说的很清楚(额,就这么认为吧,注意一下right,down的实际意义也我英语不好也不好妄加评论),简单的看一下那几个Is........函数就明白是用位保存信息了。
3、设计树的保存结构。一个好的结构可以用尽可能少的空间保存尽可能多的信息。其实这一步我没有做什么,程序延续了.lib程序第一字节为招法坐标(但是修改为横坐标0开始),将后续字节压缩为1字节,其中只含有走法好坏程度、是否有右侧节点,是否达到叶节点信息。为了进一步减少程序大小,把得到的开局库用gzip压缩一下,得到了一个1.3M的开局库,其中有近200万局面。
4、树的恢复:因为保存了是否有右侧节点、是否是叶节点信息,所以树很好恢复,只需要准备一个stack,遇到一个有右侧节点的进一个,遇到一个前一个节点是叶节点的出一个并连接。这就可以把树组织好,但是由于这种结构怎么说呢,我用起来不顺手,于是在读的时候,顺便确定了每个节点的父、全部子、左、右节点,搞得四通八达,可后来看来看去,占用内存太多,只保留了全部子节点,其他信息都去掉了,也就是说,在下面的示范代码中,只知道一个节点的全部子节点(其实和没改没啥太大区别,知道一个节点的第一个子节点和子节点的右节点……的右节点………………的右节点是一个事,几种表示和分散表示的差异而已,不过还是这样顺手啊)。
5、树查询:因为我们已经知道一个节点的全部子节点,所以可以很容易的按层查询。
6、对等局面查询:因为开局库当中对旋转0、90、180、270度,X、Y、两对角线对称这8中情况进行合并,统一用旋转0度局面表示,所以查询时需要旋转及对称。而有些不规范的表示方法(这里特指不按下子顺序记录)需要进行列出各种排列情况逐一搜索(这不在我们应该支持的范围内)。然后就是旋转或对称之后查询结果的逆转换,这个应该很简单,旋转的继续,对称的再对称就行了。
下面列出一些源码和其中使用到的小技巧:
首先,解决旋转和对称这里,逐个if 或select比较烦,所使用委托数组:
Private Delegate Function ConvertPoint(p As Byte) As Byte
Private ConvertFuns() As ConvertPoint = New ConvertPoint() {New ConvertPoint(AddressOf RotatePoint0), _
New ConvertPoint(AddressOf RotatePoint90), _
New ConvertPoint(AddressOf RotatePoint180), _
New ConvertPoint(AddressOf RotatePoint270), _
New ConvertPoint(AddressOf vFlipPoint), _
New ConvertPoint(AddressOf hFlipPoint), _
New ConvertPoint(AddressOf dlFlipPoint), _
New ConvertPoint(AddressOf drFlipPoint)}
其次,解决转换函数调用优先度问题,因为同一局面而言,一般说来,分支多位于相同转换下,这样有一个优先度设计的话可以更高效。程序的解决办法是让上次适用的转换下次第一个被调用。其实这实现起来很简单:
Private CallSequence As New List(Of Integer)
在查找到某个局面的时候,这样操作:
ElseIf (SearchResults And InVCSPos) = InVCSPos Then '局面进入黑方VCT/VCF
'因为找到的是一个index,所以只要记录转换顺序表就可以了。
CallSequence.Remove(j) '把找到结果的转换方式放在最后,下次查找时第一个调用它。
CallSequence.Add(j)
'逆转换表也要进行同样操作以保证一致性
InverseConversion.Remove(j)
InverseConversion.Add(j)
Return SearchResults
于是,上次被调用的就在list的最后一个元素了。。。想先调用它很简单啊,遍历的时候倒着…………
'逐个测试转换
For j As Integer = CallSequence.Count - To Step - '按照转换函数优先顺序依次调用它们
上面代码还涉及到了逆转换List,它和转换list进行同样的维护,因为程序只在new函数中初始化它一次:
'初始化逆转换表
InverseConversion.Add() '未旋转的逆转换就是自身
InverseConversion.Add() '旋转90度的逆转换是继续旋转270
InverseConversion.Add() '同上
InverseConversion.Add() '同上
InverseConversion.Add() '对称的逆转换就是自身
InverseConversion.Add() '同上
InverseConversion.Add() '同上
InverseConversion.Add() '同上
其实单独看,这个逆转换list是不必要的,我们只需要记录一个值就可以了,但程序中它还有其他作用,而且这样逻辑上也更一致。
接下来贴一下degzip的代码,网上有很多不太对头的……而且微软示例里面对+100也没什么解释。
'解压缩文件,因为T14.ZOB是使用GZIP压缩的,所以要把它解压之后才能分析关系树进而初始化开局库。
Private Function zobDeZip(data() As Byte) As Byte()
Dim ret() As Byte '返回值
Dim sms As New IO.MemoryStream(data) '未解压的数据流
Dim gzip As New IO.Compression.GZipStream(sms, IO.Compression.CompressionMode.Decompress, True)
Dim ms As New IO.MemoryStream '解压后的数据流
Dim DeZipBuffLen As Integer = &HFF '每次解压的字节数
Dim buf(DeZipBuffLen - ) As Byte, i As Integer = '解压缓冲区
While True '循环解压
i = gzip.Read(buf, , DeZipBuffLen)
If i = Then Exit While
ms.Write(buf, , i)
End While
ret = ms.ToArray '返回值
sms.Close() '清理工作
ms.Close()
gzip.Close()
Return ret
End Function
然后就是扫描,简单的循环嵌套就不说了,无非是从给定走法路线上摘下来一个,找到子,找不到的就是脱谱,找到叶节点的就是正在被黑棋VCT/VCF,处于枝干的就是走法了。对于黑棋根据好坏评价信息随机选取一个走法,对于白棋,最好是找到比较长的局面,因为短的黑棋出错的几率很小啊,VCT/VCF再很强,不如直接认输……但是程序没有这样做,也许知道的太多也不是一件好事。。。。例如对方眼抽筋……手抽筋……脑抽筋……各种抽筋都可能导致翻身嘛…………有兴趣的可以看看关于残局库的一些讨论。所以白棋这里,程序采用了一个小递归,来统计叶节点出现的深度:
Private Function GetLeafDepth(CurNode As BookNode, nDepth As Integer) As Integer
If (CurNode.Info And Right) = Right Then Return -nDepth '达到叶节点位置
Dim Depth As Integer
For i = To CurNode.Children.Count -
Depth = GetLeafDepth(CurNode.Children(i), nDepth + )
If Depth < Then Exit For
Next
Return Depth
End Function
这样就可以知道某一个节点下,叶节点所处的深度(额,当然,是负的表示)。
最后,罗列一下旋转和对称的代码:(不含旋转0度^_^)
'顺时针旋转90度
Private Function RotatePoint90(p As Byte) As Byte
Return ((p And &HF) << ) Or (&HE - (p >> ))
End Function '顺时针旋转180度
Private Function RotatePoint180(p As Byte) As Byte
Return (&HE - (p And &HF)) Or (&HE0 - (p And &HF0))
End Function '顺时针旋转270度
Private Function RotatePoint270(p As Byte) As Byte
Return ((&HE - (p And &HF)) << ) Or ((p >> ))
End Function '竖直翻转
Private Function vFlipPoint(p As Byte) As Byte
Return (p And &HF) Or (&HE0 - (p And &HF0))
End Function '水平翻转
Private Function hFlipPoint(p As Byte) As Byte
Return (&HE - (p And &HF)) Or (p And &HF0)
End Function '左上右下对角线
Private Function dlFlipPoint(p As Byte) As Byte
Return ((p And &HF) << ) Or ((p And &HF0) >> )
End Function '右上左下对角线
Private Function drFlipPoint(p As Byte) As Byte
Return (&HE - (p And &HF) << ) Or (&HE0 - (p And &HF0) >> )
End Function
E、E0说明了棋盘的宽度和高度为15*15。
【五子棋AI循序渐进】——开局库的更多相关文章
- 【五子棋AI循序渐进】关于VCT,VCF的思考和核心代码
前面几篇发布了一些有关五子棋的基本算法,其中有一些BUG也有很多值得再次思考的问题,在框架和效果上基本达到了一个简单的AI的水平,当然,我也是初学并没有掌握太多的高级技术.对于这个程序现在还在优化当中 ...
- 【五子棋AI循序渐进】——多线程搜索
关于多线程搜索,有很多方法来实现,很多文章推荐基于MTD(F)的方式.好处不言而喻,不过我的程序中采用的是基于PVS的多线程搜索.实现起来主要是这几个方面问题需要解决: 1.置换表的互斥访问. 2.局 ...
- 【五子棋AI循序渐进】——整合完成
经过一年多的学习和探索,终于在今天得到了一些回报,在实现PVS多线程和加入了一个新的启发模式之后,搜索速度达到了120K左右,现在整合了VCF/VCT引擎.PVS混合引擎之后,棋力与连珠fiver6基 ...
- 五子棋AI清月连珠开源
经过差不多两年的业余时间学习和编写,最近把清月连珠的无禁手部分完善得差不多了.这中间进行了很多思考,也有很多错误认识,到现在有一些东西还没有全面掌握,所以想通过开源于大家共同交流. 最近一直发表一些五 ...
- 使用QT creator实现一个五子棋AI包括GUI实现(8K字超详细)
五子棋AI实现 五子棋游戏介绍 五子棋的定义 五子棋是全国智力运动会竞技项目之一,是具有完整信息的.确定性的.轮流行动的.两个游戏者的零和游戏.因此,五子棋是一个博弈问题. 五子棋的玩法 五子棋有两种 ...
- 五子棋AI大战OC实现
Gobang 五子棋AI大战,该项目主要用到MVC框架,用算法搭建AI实现进攻或防守 一.项目介绍 1.地址: github地址:Gobang 2.效果图: 二.思路介绍 大概说下思路,具体看代码实现 ...
- 五子棋AI教程
https://github.com/Chuck-Ai/gobang 我写了非常详细的中文教程,教你如何一步步编写自己的五子棋AI: 五子棋AI设计教程第二版一:前言 五子棋AI设计教程第二版二:博弈 ...
- 五子棋AI的思路
隔了一年才把AI思路给写了... 需求分析与设计方案:http://www.cnblogs.com/songdechiu/p/4951634.html 如需整个工程,移步http://download ...
- 人机ai五子棋 ——五子棋AI算法之Java实现
人机ai五子棋 下载:chess.jar (可直接运行) 源码:https://github.com/xcr1234/chess 其实机器博弈最重要的就是打分,分数也就是权重,把棋子下到分数大的地方, ...
随机推荐
- pointers on c (day 1,chapter1)
c语言的优先级 优先级 运算符 名称或含义 使用形式 结合方向 说明 1 [] 数组下标 数组名[常量表达式] 左到右 () 圆括号 (表达式)/函数名(形参表) . 成员选择(对象) 对象.成员名 ...
- iOS学习32之UIKit框架-可视化编程-XIB
1. Interface Builder 可视化编程 1> 概述 GUI : 图形用户界面(Graphical User Interface, 简称GUI, 又称图形化界面) 是指采用图形方式显 ...
- 【NOI2016】区间 题解
题目大意: 有n个区间,当有m个区间有公共部分时,求m个区间长度的最大值与最小值之差的最小值. 思路: 按区间的长度从小到大排序,可知连续的几个区间最优,则用两个指针指其头尾,线性扫描,再用线段树区间 ...
- Codeforces Round #252 (Div. 2) A - Valera and Antique Items
水题 #include <iostream> #include <set> #include <vector> #include <algorithm> ...
- 【BZOJ】3529: [Sdoi2014]数表
题意:求 $$\sum_{i=1}^{n} \sum_{j=1}^{m} \sum_{d|(i, j)} d 且 (\sum_{d|(i, j)} d)<=a$$ n, m<=1e5,q次 ...
- 【BZOJ2002】 [Hnoi2010]Bounce 弹飞绵羊 分块/LCT
Description 某天,Lostmonkey发明了一种超级弹力装置,为了在 他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装 ...
- 【noiOJ】p1794
t1794:集合加法 查看 提交 统计 提问 总时间限制: 3000ms 内存限制: 65536kB 描述 给出2个正整数集合A = {pi | 1 <= i <= a},B = {q ...
- dataTransfer.getData()在dragover,dragenter,dragleave中无法获取数据的问题
做拖拽相关效果时,想在ondragover时给被拖拽元素添加一些样式,于是在dragover事件的函数中通过dataTransfer.getData()获取在dragstart中设置的数据,然而发现d ...
- mvc获取时间戳
DateTime dtStart = TimeZone.CurrentTimeZone.ToLocalTime(, , )); DateTime dtNow = DateTime.Parse(Date ...
- 一段能导致火狐、谷歌Safari浏览器崩溃,甚至让iPhone重启的代码
JavaScript代码,能导致火狐.谷歌Safari浏览器崩溃,甚至让iPhone重启 <html> <body> <script> var total = &q ...