• 【五子棋AI循序渐进】发布一个完整的有一定棋力的版本(含源码)


          经过这半年左右的学习和探索,现在对五子棋AI有了一定的认识,给大家发出来现在的版本。因为最近有些事情很生气,要是年轻时真就先灭了这些王八羔子,省的它们继续祸害好人。不过它们也祸害不了几天了,祸害人者人祸害之。心情不好,就少打几个字,说一下基本思路:

    1、每一个点的重要性,决定于四个方向上的棋型;棋型是可以相互转化的,可以枚举出每一种变化以及它们之间的关联关系。

        例如:(0=白、1=黑、2=空,程序中和下面全文均如此)

        一行空棋 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 当白棋要下的时候,就要考察更好的点,我们如果给这一行棋评分如下

                    0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0  那么,白棋的走法生成器就会知道1的那些点,排在0前面。同样道理,

         一行棋型 2   2   2   1   1   1   2   2   2   2   2   2   2   2   2   2 当白棋要下的时候,就会选择分数更高的点先进行测试:

                      2   4  8  -1  -1  -1   8   4   2   1   1   1   1   1   1   0 于是会先测试8分,然后4分,然后2分1分,当然,因为8分点已经可以导致胜利(活4)那么可以不生成其他点。而此时如果我们下在第3个位置上,即第一个评分为8的点上,则得到棋型:

         一行棋型 2   2   1   1   1   1   2   2   2   2   2   2   2   2   2   2  对这个棋型的评分我们也可以预先评价好:

                     4   F  -1  -1  -1  -1   F   4   2   1   1   1   1   1   1   0 

    所以,我们可以建立一个结构数组来保存棋型及其对应的各个点的冲棋值,这样很容易得到下某一个点后的新棋盘评价。

    2、VCT\VCF。这个话题可以说是五子棋中非常重要的,可以说一个AI的VCT\VCF能力体现了它的棋力(呵呵,不过我的现在还不怎么样)。我没有看到这方面的源码,但实际上,VCN搜索无非是象棋中的“将军延伸”技术而已!虽然我的代码中我进行了一些修改而且看起来不伦不类(因为没有详细的记录每一方的冲棋程度),但我在网上搜索时经常看到有人问你的VCT,VCF做的怎么样了?我就很茫然的说……

    3、走法顺序。这确实是一个非常值得深入思考的问题,但是从冲棋点的角度来考虑,这似乎不是问题,我们完全可以根据冲棋点分值大小进行排列,可实际上代码会很长,至少我的程序里面它是仅次于剪裁函数的家伙,而且我对那些代码很不满意。

    好了,贴上一些核心代码,说明一下:

    Public Class mShape529
        Public tShapeObj() As mShape529             '转换结果的引用
        Public cLine() As mConstValue.LinkType      '冲棋信息(由空点决定)
    
        Sub New(len As Integer)
            ReDim tShapeObj(len * 3 - 1)
            ReDim cLine(len * 2 - 1)
        End Sub
    
        Public Overrides Function ToString() As String
            Dim tmp As String = String.Empty
            For i As Integer = 0 To cLine.Length - 1
                tmp &= cLine(i).ToString & " "
                If i + 1 = cLine.Length \ 2 Then tmp &= " |  "
            Next
            Return tmp
        End Function
    
    End Class
    
    Public Class mShapeManeger
        Private Shared allShapes(4) As List(Of mShape529)    '长度为len的全部形态
    
        Shared Sub New()
            Dim i As Integer
            For i = 0 To 4
                allShapes(i) = New List(Of mShape529)
                allShapes(i) = ReadByteFile59(i + 5)
            Next
        End Sub
    
        '返回指定长度的模板
        Public Shared ReadOnly Property ShapeList(len As Integer) As List(Of mShape529)
            Get
                Return allShapes(IIf(len > 9, 4, len - 5))
            End Get
        End Property
    
        Private Shared Function ReadByteFile59(len As Integer) As List(Of mShape529)
            ' tShape() As Integer               'len*3*2
            ' cLine() As byte                   'len*2
            Dim bytes() As Byte = My.Resources.ResourceManager.GetObject("_" & len)
            Dim i, j, l As Integer, tmps(1) As Byte
            Dim ret As New List(Of mShape529)
            Dim stp As Integer = len * 3 * 2 + len * 2
            Dim tmpint As Integer
            For i = 0 To bytes.Length - 1 Step stp
                ret.Add(New mShape529(len))
            Next
            For i = 0 To ret.Count - 1
                Dim tmp = ret(i)
                For j = 0 To len * 3 - 1
                    tmps(0) = bytes(l)
                    tmps(1) = bytes(l + 1)
                    l += 2
                    tmpint = CInt(BitConverter.ToInt16(tmps, 0))
                    If tmpint <> -1 Then tmp.tShapeObj(j) = ret(tmpint)
                Next
                For j = 0 To len * 2 - 1
                    Select Case bytes(l)
                        Case 0
                            tmp.cLine(j) = mConstValue.LinkTypelnl
                        Case 1
                            tmp.cLine(j) = mConstValue.LinkTypel00
                        Case 2
                            tmp.cLine(j) = mConstValue.LinkTypel11
                        Case 3
                            tmp.cLine(j) = mConstValue.LinkTypel12
                        Case 4
                            tmp.cLine(j) = mConstValue.LinkTypel21
                        Case 5
                            tmp.cLine(j) = mConstValue.LinkTypel22
                        Case 6
                            tmp.cLine(j) = mConstValue.LinkTypel31
                        Case 7
                            tmp.cLine(j) = mConstValue.LinkTypel32
                        Case 8
                            tmp.cLine(j) = mConstValue.LinkTypel32
                        Case 9
                            tmp.cLine(j) = mConstValue.LinkTypel41
                        Case 10
                            tmp.cLine(j) = mConstValue.LinkTypel415
                        Case 11
                            tmp.cLine(j) = mConstValue.LinkTypel42
                        Case 12
                            tmp.cLine(j) = mConstValue.LinkTypel50
                        Case 13
                            tmp.cLine(j) = mConstValue.LinkTypel60
                        Case 14
                            tmp.cLine(j) = mConstValue.LinkTypel70
                    End Select
                    l += 1
                Next
                ret(i) = tmp
            Next
            Return ret
        End Function
    
    End Class

    上面是基础形态和基础形态管理器,思路是5-14长度的72个向量中,5-9长度的,直接使用生成好的模板,而10-14的,利用9长度的模板进行合成。因为这是初始化时的代码不影响计算速度,所以没有任何优化。

    Public Class mVector52E
        Private mss As List(Of mShape529)           '当前形态模板
        Public len As Byte                          '向量长度
        Public shapes() As mShape529                '所包含的形态
        Public cLine() As Integer                   '冲棋信息
        Public key As Integer                       '键。由低20-30位记录形态。同一位置用2位表示,白棋在低位黑棋在高位。没有初始化的必要。
        Private ps() As Byte                        '包含的棋盘点(实际坐标)。
        Private dx, dy As Integer                   '方向:右=(1,0),下=(0,1),右上=(1,-1),左上=(-1,-1)
    
        Sub New(points() As Byte, xoffset As Integer, yoffset As Integer)
            Dim i As Integer
            len = points.Length
            ReDim ps(len - 1)
            Array.Copy(points, ps, len)
            dx = xoffset
            dy = yoffset
            '本向量对应的形态模板
            mss = mShapeManeger.ShapeList(len)
            '定义冲棋信息
            ReDim cLine(len * 2 - 1)
            '若长度为9以内,则用一个长度相等的形态表示即可。否则用一组长度为9的形态表示。
            If len <= 9 Then
                ReDim shapes(0)
                shapes(0) = mss(0)
            Else
                ReDim shapes(len - 9)
                For i = 0 To shapes.Length - 1
                    shapes(i) = mss(0)
                Next
            End If
        End Sub
    
        Sub SetPlayer(point As Byte, player As Integer)
            Dim i, j, p As Integer, tkm, tks As Integer
            Dim n As Integer = Math.Min(len - 1, 8)     '最大下标
            Dim ts As mShape529
            '更新所属形态
            For i = 0 To shapes.Length - 1
                p = point - i
                '当点在需要更新的形态内
                If p > -1 AndAlso p <= n Then
                    ts = shapes(i).tShapeObj(3 * p + player)
                    If ts Is Nothing Then
                        Throw New Exception("该点已经有子")
                    Else
                        shapes(i) = ts
                    End If
                End If
            Next
            '更新key和检查置换表。
            Dim keyindex As Integer = (point - 2) * 2
            If player = 2 Then
                key = key And Not (1 << keyindex)           '删除白棋
                key = key And Not (1 << keyindex + 1)       '删除黑棋
            Else
                key = key Or (1 << keyindex + player)       '设置棋子
            End If
            If len > 9 AndAlso mZobristForVector.ProbeHash(Me) Then Return
    
            '清理冲棋信息
            For i = 0 To len * 2 - 1
                cLine(i) = 0
            Next
    
            '由子形态合成向量冲棋信息
            For i = 0 To shapes.Length - 1
                ts = shapes(i)
                For j = 0 To n
                    tkm = cLine(j + i)
                    tks = ts.cLine(j)
                    If tks > tkm OrElse tks = mConstValue.LinkTypelnl OrElse tks = mConstValue.LinkTypel70 Then cLine(j + i) = tks
                    tkm = cLine(j + i + len)
                    tks = ts.cLine(j + n + 1)
                    If tks > tkm OrElse tks = mConstValue.LinkTypelnl OrElse tks = mConstValue.LinkTypel70 Then cLine(j + i + len) = tks
                Next
            Next
            '保存到置换表
            If len > 9 Then mZobristForVector.RecordHash(Me)
        End Sub
    
        Function InLine(p As Byte) As Boolean
            Dim i As Integer
            For i = 0 To ps.Length - 1
                If ps(i) = CByte(p) Then Return True
            Next
            Return False
        End Function
    
        Sub Clear()
            Dim i As Integer
            key = 0
            For i = 0 To shapes.Length - 1
                shapes(i) = mss(0)
            Next
        End Sub
    
        Public Function BoardPoint2VectorPoint(p As Byte) As Byte
            '右=(1,0),下=(0,1),右上=(1,-1),左上=(-1,-1)
            If dy = 0 Then  '
                '0 1 2 3 4 5 …… 14
                '1 2 3 4 5 6 …… 15
                Return p - ps(0)
            End If
            If dx = 0 Then  '
                ' 0 15 30 45
                ' 1 16 31 46
                Return (p - ps(0)) / 15
            End If
            If dx = 1 Then  '右上
                '60 46 32 18  4
                '75 61 47 33 19 5
                Return (ps(0) - p) / 14
            End If
            If dx = -1 Then '左上
                '214 198 182 166 150
                '209 193 177 161
                Return (ps(0) - p) / 16
            End If
            Throw New Exception("err")
        End Function
    
        Public Function VectorPoint2BoardPoint(p As Byte) As Byte
            '右=(1,0),下=(0,1),右上=(1,-1),左上=(-1,-1)
            If dy = 0 Then  '
                '0 1 2 3 4 5 …… 14
                '1 2 3 4 5 6 …… 15
                Return p + ps(0)
            End If
            If dx = 0 Then  '
                ' 0 15 30 45
                ' 1 16 31 46
                Return p * 15 + ps(0)
            End If
            If dx = 1 Then  '右上
                '60 46 32 18  4
                '75 61 47 33 19 5
                Return ps(0) - p * 14
            End If
            If dx = -1 Then '左上
                '214 198 182 166 150
                '209 193 177 161
                Return ps(0) - p * 16
            End If
            Throw New Exception("err")
        End Function
    
        Public Overrides Function ToString() As String
            Dim tmp As String = String.Empty
            For i As Integer = 0 To len * 2 - 1
                tmp &= ps(i) & Space(6 - cLine(i).ToString.Length) & cLine(i) & vbCrLf
            Next
            Return tmp
        End Function
    
    End Class
    Public Class mVectorManager
        '所有行
        Public AllVectors(71) As mVector52E
        '点对应的行
        Public VectorsOfPoint(224)() As mVector52E
    
        Sub New()
            '求所有的向量
            Dim x, y, n, levindex As Integer
            Dim lev(4) As Integer
            '右,0-14
            For y = 0 To 14
                AllVectors(n) = GetVector(0, y, 14, y, 1, 0)
                n += 1
            Next
            levindex += 1
            lev(levindex) = n
            '
            For x = 0 To 14
                AllVectors(n) = GetVector(x, 0, x, 14, 0, 1)
                n += 1
            Next
            levindex += 1
            lev(levindex) = n
            '右上
            For y = 4 To 14
                AllVectors(n) = GetVector(0, y, y, 0, 1, -1)
                n += 1
            Next
            For x = 1 To 10
                AllVectors(n) = GetVector(x, 14, 14, x, 1, -1)
                n += 1
            Next
            levindex += 1
            lev(levindex) = n
            '左上
            For x = 4 To 14
                AllVectors(n) = GetVector(x, 14, 0, 14 - x, -1, -1)
                n += 1
            Next
            For y = 13 To 4 Step -1
                AllVectors(n) = GetVector(14, y, 14 - y, 0, -1, -1)
                n += 1
            Next
            levindex += 1
            lev(levindex) = n
            '分配到点记录表
            Dim i As Integer
    
            For y = 0 To 14
                For x = 0 To 14
                    Dim ls(3) As mVector52E
                    '遍历全部向量,将点所在的向量保存到ls。
                    For levindex = 0 To 3
                        For i = lev(levindex) To lev(levindex + 1) - 1
                            Dim tmpvector As mVector52E = AllVectors(i)
                            If tmpvector.InLine(y * 15 + x) Then
                                ls(levindex) = tmpvector
                                Exit For
                            End If
                        Next
                    Next
                    VectorsOfPoint(y * 15 + x) = ls
                Next
            Next
        End Sub
    
        '根据起点终点初始化全部坐标点(用一个字节表示)
        Private Function GetVector(x1 As Integer, y1 As Integer, x2 As Integer, y2 As Integer, dx As Integer, dy As Integer) As mVector52E
            '向量上的全部点。
            Dim ps() As Byte = Nothing
            '当前坐标X,Y,记数。
            Dim x As Integer = -1, y As Integer = -1, cst As Integer
            '从向量起点遍历,直到终点,把每一个点记录下来。
            Do Until x = x2 AndAlso y = y2
                x = x1 + dx * cst
                y = y1 + dy * cst
                ReDim Preserve ps(cst)
                ps(cst) = y * 15 + x        '将坐标转换为数组下标
                cst += 1
            Loop
            '将向量分割为长度5-9的若干个子向量。
            Return New mVector52E(ps, dx, dy)
        End Function
    
        Public Sub Clear()
            Dim i As Integer
            For i = 0 To 71
                AllVectors(i).Clear()
            Next
        End Sub
    
    End Class
    
    Public Class mZobristForVector
    
        Private Structure mVectorItem
            Public cLine() As Integer               '冲棋信息,30
            Public key As Integer                   '键,31
            Public len As Integer                   '长,32
    
            Sub New(vlen As Integer)
                len = vlen  '因为10-14长度都保存在一个表里,而key的计算方法是按位排列,所以重复非常多,必须用len加以区分。覆盖策略是长度大的优先保存。
                ReDim cLine(vlen - 1)
            End Sub
    
            Shared Sub Clear(ByRef mvi As mVectorItem)
                mvi.key = -1
                mvi.len = mConstValue.ZeroLinkArrLen
                ReDim mvi.cLine(mConstValue.ZeroLinkArrLen - 1)
            End Sub
    
        End Structure
    
        Private Shared hstb(mConstValue.HASH_SIZEOFVECTOR - 1) As mVectorItem   '
    
        Shared Sub New()
            Dim i As Integer
            For i = 0 To mConstValue.HASH_SIZEOFVECTOR - 1
                hstb(i) = New mVectorItem(mConstValue.ZeroLinkArrLen - 1) '用最长长度(30)来初始化,这样每一项大小一样大。
                mVectorItem.Clear(hstb(i))
            Next
        End Sub
    
        Shared Sub Clear()
            Dim i As Integer
            For i = 0 To mConstValue.HASH_SIZEOFVECTOR - 1
                mVectorItem.Clear(hstb(i))
            Next
        End Sub
    
        '保存置换表项。返回值:0=未替换,1=替换空项,2=替换已有项。
        Shared Function RecordHash(vector As mVector52E) As Integer
            Dim ret As Integer = 0
            Dim hsh As mVectorItem = hstb(vector.key And (mConstValue.HASH_SIZEOFVECTOR - 1))
            '空项
            If hsh.key = -1 Then
                ret = 1
            Else
                '已有项长度小于等于新长度,且最大冲棋值小于等于要保存值
                If hsh.len <= vector.len Then ret = 2
            End If
            '替换
            If ret > 0 Then
                Array.Copy(vector.cLine, hsh.cLine, vector.len * 2)
                hsh.key = vector.key
                hsh.len = vector.len
            End If
            hstb(vector.key And (mConstValue.HASH_SIZEOFVECTOR - 1)) = hsh
            Return ret
        End Function
    
        '提取置换表项。返回值表示是否成功。
        Shared Function ProbeHash(vector As mVector52E) As Boolean
            Dim hsh As mVectorItem = hstb(vector.key And (mConstValue.HASH_SIZEOFVECTOR - 1))
            '空项或不等
            If hsh.len <> vector.len OrElse hsh.key <> vector.key Then Return False
            '返回置换表项
            Array.Copy(hsh.cLine, vector.cLine, vector.len * 2)
            Return True
        End Function
    
    End Class

    上面是向量和向量管理器以及对应的置换表的代码。向量一共有72个,都存储在管理器中。用9长度合成10-14的长度,并且计算点所对应的向量,实现下子、提子函数。

    Public Class mPosition
        '轮到谁走,0=白方,1=黑方
        Public sdPlayer As Integer
        '距离根节点的步数
        Public nDistance As Integer
        '电脑走的棋
        Public mvResult As Integer
        '各点的冲棋值表
        Public cpInfo() As Integer
        '待排序坐标表
        Dim pslst(1)() As Byte
        '根据cpInfo排序
        Dim cplst(1)() As Integer
        '向量管理
        Public mVectorManager As New mVectorManager
        '当前局面密匙结构
        Public poskey As mZobristForPosition.mPosKey
    
        Sub New()
            sdPlayer = 1
            StartUp()
            ReDim cpInfo(mConstValue.ZerocpPosArrLen - 1)
            ReDim pslst(0)(mConstValue.BoardSize - 1)
            ReDim pslst(1)(mConstValue.BoardSize - 1)
            ReDim cplst(0)(mConstValue.BoardSize - 1)
            ReDim cplst(1)(mConstValue.BoardSize - 1)
            Array.Copy(mConstValue.BoardPointList, pslst(0), mConstValue.BoardSize)
            Array.Copy(mConstValue.BoardPointList, pslst(1), mConstValue.BoardSize)
            mVectorManager.Clear()
            mZobristForPosition.Clear()
            mZobristForPosition.mPosKey.Clear(poskey)
            mZobristForVector.Clear()
        End Sub
    
        '清理变化,恢复初始值。
        Public Sub StartUp()
            nDistance = 0
            mvResult = -1
        End Sub
    
        '设置棋盘上点的棋子.
        Public Sub SetPlayer(point As Byte, player As Integer)
            SyncLock cpInfo
                Dim i, j As Integer
                '若是下一个空子(撤销招法),则局面更改玩家为上一步玩家、步数减一;否则,局面更改为当前玩家,步数加一。
                If player = 2 Then
                    poskey = mZobristForPosition.SetPlayer(poskey, point, 1 - sdPlayer)     '更新局面KEY
                    nDistance -= 1                                                          '更新走棋步数
                Else
                    poskey = mZobristForPosition.SetPlayer(poskey, point, sdPlayer)
                    nDistance += 1
                End If
                '在指定点上下一个白、黑或空子(撤销招法)。
                Dim tmpvector As mVector52E
                Dim tmpPoint As Integer = -1
                For i = 0 To 3
                    tmpvector = mVectorManager.VectorsOfPoint(point)(i)
                    If tmpvector IsNot Nothing Then
                        If tmpvector.key <> 0 Then      '只更新有子向量
                            '冲棋表更新第一步:删除原向量产生的影响
                            For j = 0 To tmpvector.len - 1
                                tmpPoint = tmpvector.VectorPoint2BoardPoint(j)
                                cpInfo(tmpPoint) -= tmpvector.cLine(j)
                                cpInfo(tmpPoint + mConstValue.BoardSize) -= tmpvector.cLine(j + tmpvector.len)
                            Next
                        End If
                        tmpvector.SetPlayer(tmpvector.BoardPoint2VectorPoint(point), player)
                        '冲棋表更新第二步:添加新向量的影响
                        If tmpvector.key <> 0 Then
                            For j = 0 To tmpvector.len - 1
                                tmpPoint = tmpvector.VectorPoint2BoardPoint(j)
                                cpInfo(tmpPoint) += tmpvector.cLine(j)
                                cpInfo(tmpPoint + mConstValue.BoardSize) += tmpvector.cLine(j + tmpvector.len)
                            Next
                        End If
                    End If
                Next
                '最后,交换走棋方。
                sdPlayer = 1 - sdPlayer
            End SyncLock
        End Sub
    
        '进行粗略估值
        '已胜利局面中有5个以上2560-N,实际上有一个点大于1024即可判定胜负。
        '一个点上两个活三或更多则可以杀棋,即32*2就是杀棋。
        '一个点上一个活三或更多则是冲棋,即32以上就是冲棋。
        Function Evaluate() As Integer
            SyncLock cpInfo
                Dim csPlayer As Integer = 1 - sdPlayer              '对方
                Dim vl(1) As Integer                                '总分
                Dim curcpInfocLine(1) As Integer                    '当前冲棋值
                '分离
                Array.Copy(cpInfo, 0, cplst(0), 0, mConstValue.BoardSize)
                'CopyMemory(cplst(0), cpInfo, mConstValue.BoardSize)
                Array.Copy(cpInfo, mConstValue.BoardSize, cplst(1), 0, mConstValue.BoardSize)
                '排序
                Array.Sort(cplst(0))
                Array.Sort(cplst(1))
                '遍历
                For i = mConstValue.BoardSize - 1 To 0 Step -1
                    curcpInfocLine(0) = cplst(0)(i)
                    curcpInfocLine(1) = cplst(1)(i)
                    '已有一方胜利
                    If curcpInfocLine(csPlayer) >= mConstValue.WIN_VALUE Then Return -mConstValue.MATE_VALUE
                    If curcpInfocLine(sdPlayer) >= mConstValue.WIN_VALUE Then Return mConstValue.MATE_VALUE
                    '有2个或更多成5(或长连)点
                    If curcpInfocLine(csPlayer) >= mConstValue.LinkTypel50 AndAlso cplst(csPlayer)(i - 1) >= mConstValue.LinkTypel50 Then Return -mConstValue.MATE_VALUE
                    If curcpInfocLine(sdPlayer) >= mConstValue.LinkTypel50 AndAlso cplst(sdPlayer)(i - 1) >= mConstValue.LinkTypel50 Then Return mConstValue.MATE_VALUE
                    '将冲棋值大于l12的点的冲棋值之和作为评价
                    If curcpInfocLine(0) > mConstValue.LinkTypel21 Then vl(0) += curcpInfocLine(0)
                    If curcpInfocLine(1) > mConstValue.LinkTypel21 Then vl(1) += curcpInfocLine(1)
                Next
                Return vl(sdPlayer) - vl(1 - sdPlayer)
            End SyncLock
        End Function
    
        '有子棋盘
        Dim tb As New BitArray(mConstValue.BoardSize)
        '排序/分类截取
        Function NextGenerateMove(ByRef retval() As Byte, ByRef InCheck As Integer, InCheckOnly As Boolean) As Integer
            SyncLock cpInfo
                tb.SetAll(False)
                '1、排序
                Array.Copy(cpInfo, 0, cplst(0), 0, mConstValue.BoardSize)
                Array.Copy(cpInfo, mConstValue.BoardSize, cplst(1), 0, mConstValue.BoardSize)
                Array.Sort(pslst(0), New mComparer(cplst(0)))
                Array.Sort(pslst(1), New mComparer(cplst(1)))
                '2、分类截取
                Dim cnt As Integer, csPlayer As Integer = 1 - sdPlayer
                '已经有一方胜利
                If GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.WIN_VALUE) > 0 Then Return -1
                If GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.WIN_VALUE) > 0 Then Return -1
                '成五或长连
                If GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.LinkTypel50) > 0 Then
                    Return cnt - 1
                End If
                If GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.LinkTypel50) > 0 Then
                    Return cnt - 1
                End If
                '42,41+32,32+32
                If GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.LinkTypel32 * 2) > 0 Then
                    InCheck = InCheck Or (2 - csPlayer)
                End If
                If GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.LinkTypel32 * 2) > 0 Then
                    InCheck = InCheck Or (2 - sdPlayer)
                End If
                If cnt > 2 Then
                    Return cnt - 1
                Else
                    GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.LinkTypel32)
                    GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.LinkTypel32)
                    If cnt > 0 Then
                        InCheck = 0
                        Return cnt - 1
                    End If
                End If
                If InCheckOnly Then Return cnt - 1
                GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.LinkTypel31)
                GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.LinkTypel31)
                If cnt > 0 Then Return cnt - 1
                GetcplstByLinkType(cplst(csPlayer), pslst(csPlayer), retval, cnt, mConstValue.LinkTypel22)
                GetcplstByLinkType(cplst(sdPlayer), pslst(sdPlayer), retval, cnt, mConstValue.LinkTypel22)
                Return cnt - 1
            End SyncLock
        End Function
    
        Private Function GetcplstByLinkType(cplst() As Integer, pslst() As Byte, ByRef retval() As Byte, ByRef cnt As Integer, Threshold As Integer) As Integer
            Dim i, tp, tv, tcnt, bkv As Integer
            For i = 0 To mConstValue.BoardSize - 1
                tp = pslst(i)
                tv = cplst(tp)
                If tv < Threshold Then Exit For
                bkv = tv
                If tb(tp) = False Then
                    retval(cnt) = tp
                    cnt += 1
                    tcnt += 1
                    tb(tp) = True
                End If
            Next
            Return tcnt
        End Function
    
        Public Overrides Function ToString() As String
            Dim i As Integer, s As Integer
            Dim tmpstr As String = String.Empty
            For i = 0 To cpInfo.Length - 1
                tmpstr &= Space(6 - CStr(cpInfo(i)).Length) & cpInfo(i)
                If i + 1 <= cpInfo.Length / 2 Then s = 15 Else s = 30
                If ((i + 1) Mod 15) = 0 Then tmpstr &= Space(6) & (s - (i \ 15)) & "  " & (i Mod mConstValue.BoardSize) & vbCrLf
                If i + 1 = cpInfo.Length / 2 Then tmpstr &= "-----A-----B-----C-----D-----E-----F-----G-----H-----I-----J-----K-----L-----M-----N-----O------" & vbCrLf
            Next
            Return tmpstr & "-----A-----B-----C-----D-----E-----F-----G-----H-----I-----J-----K-----L-----M-----N-----O------" & vbCrLf
        End Function
    
    End Class
    
    Public Class mComparer : Implements IComparer(Of Byte)
        Private cline() As Integer
        Sub New(ps() As Integer)
            cline = ps
        End Sub
    
        Public Function Compare(x As Byte, y As Byte) As Integer Implements System.Collections.Generic.IComparer(Of Byte).Compare
            Return cline(y) - cline(x)
        End Function
    
    End Class
    Imports System.Security.Cryptography
    
    Public Class mZobristForPosition
        '置换表项结构
        Private Structure mPosZobItem
            Public dwLock0 As Long                  '
            Public ucDepth As Integer               '深度
            Public ucFlag As mConstValue.HASHType   '节点类型
            Public svl As Integer                   '分值
            Public wmv As Integer                   '招法
            Public nDistance As Integer
            Public dwLock1 As Long                  '
    
            Shared Sub Clear(ByRef mzp As mPosZobItem)
                mzp.dwLock0 = 0L
                mzp.ucDepth = 0
                mzp.ucFlag = mConstValue.HASHType.HASH_ALPHA
                mzp.svl = 0
                mzp.wmv = 0
                mzp.nDistance = 0
                mzp.dwLock1 = 0L
            End Sub
        End Structure
    
        '密匙结构
        Public Structure mPosKey
            Public key As Integer                   '用以计算存储位置的键
            Public dwLock0 As Long                  '
            Public dwLock1 As Long
            Shared Sub Clear(ByRef mpk As mPosKey)
                mpk.key = 0
                mpk.dwLock0 = 0L
                mpk.dwLock1 = 0L
            End Sub
            Public Overrides Function ToString() As String
                Return "key " & Hex(key) & " dwlock0 " & Hex(dwLock0) & " dwlock1 " & Hex(dwLock1)
            End Function
            Public Overrides Function Equals(obj As Object) As Boolean
                Dim tmp As mPosKey = CType(obj, mPosKey)
                Return tmp.key = key AndAlso tmp.dwLock0 = dwLock0 AndAlso tmp.dwLock1 = dwLock1
            End Function
        End Structure
    
        '密匙流
        Private Shared table(1)() As mPosKey
    
        '置换表
        Private Shared hstb(mConstValue.HASH_SIZEOFPOS - 1) As mPosZobItem
    
        Shared Sub New()
            '初始化密匙流
            ReDim table(0)(224)
            ReDim table(1)(224)
            Dim i, j As Integer
            For i = 0 To 224
                For j = 0 To 1
                    table(j)(i).key = MD5Zob(j, i)
                    table(j)(i).dwLock0 = RC2Zob(j, i)
                    table(j)(i).dwLock1 = DESZob(j, i)
                Next
            Next
        End Sub
    
        Shared Sub Clear()
            Dim i As Integer
            For i = 0 To mConstValue.HASH_SIZEOFPOS - 1
                mPosZobItem.Clear(hstb(i))
            Next
        End Sub
    
        'MD5加密算法
        Private Shared Function MD5Zob(k1 As Integer, k2 As Integer) As Integer
            Dim md5 As New MD5CryptoServiceProvider
            Dim inputByteArray As Byte() = New Byte() {k1, k2}
            Dim mdByte As Byte() = md5.ComputeHash(inputByteArray)
            Return BitConverter.ToInt32(mdByte, 0)
        End Function
    
        'RC2,DES算法的键和动量
        Private Shared key As Byte() = New Byte() {&H12, &H34, &H56, &H78, &H90, &HAB, &HCD, &HEF}
        Private Shared iv As Byte() = New Byte() {&H23, &H34, &H45, &H56, &H67, &H78, &H89, &H9A}
    
        'RC2加密算法
        Private Shared Function RC2Zob(k1 As Byte, k2 As Byte) As Long
            Dim rc2 As New RC2CryptoServiceProvider
            Dim inputByteArray As Byte() = New Byte() {k1, k2}
            rc2.Key = key
            rc2.IV = iv
            Dim ms As New System.IO.MemoryStream
            Dim cs As New CryptoStream(ms, rc2.CreateEncryptor, CryptoStreamMode.Write)
            cs.Write(inputByteArray, 0, inputByteArray.Length)
            cs.FlushFinalBlock()
            Return BitConverter.ToInt64(ms.ToArray(), 0)
        End Function
    
        'DES加密算法
        Private Shared Function DESZob(k1 As Byte, k2 As Byte) As Long
            Dim rc2 As New DESCryptoServiceProvider
            Dim inputByteArray As Byte() = New Byte() {k1, k2}
            rc2.Key = key
            rc2.IV = iv
            Dim ms As New System.IO.MemoryStream
            Dim cs As New CryptoStream(ms, rc2.CreateEncryptor, CryptoStreamMode.Write)
            cs.Write(inputByteArray, 0, inputByteArray.Length)
            cs.FlushFinalBlock()
            Return BitConverter.ToInt64(ms.ToArray(), 0)
        End Function
    
        '获取新键值和锁
        Public Shared Function SetPlayer(poskey As mPosKey, point As Integer, player As Integer) As mPosKey
            Dim tmp As mPosKey = table(player)(point)
            Dim ret As New mPosKey
            ret.key = poskey.key Xor tmp.key
            ret.dwLock0 = poskey.dwLock0 Xor tmp.dwLock0
            ret.dwLock1 = poskey.dwLock1 Xor tmp.dwLock1
            Return ret
        End Function
    
        '提取置换表项。
        Public Shared Function ProbeHash(poskey As mPosKey, vlAlpha As Integer, vlBeta As Integer, nDepth As Integer, nDistance As Integer, ByRef mv As Integer) As Integer
            SyncLock hstb
                Dim bMate As Boolean                                                            '杀棋标志:如果是杀棋,那么不需要满足深度条件
                Dim hsh As mPosZobItem = hstb(poskey.key And (mConstValue.HASH_SIZEOFPOS - 1))         '用and运算代替mod运算
                If (hsh.dwLock0 <> poskey.dwLock0) OrElse (hsh.dwLock1 <> poskey.dwLock1) Then  '未找到
                    mv = -1
                    Return -mConstValue.MATE_VALUE
                End If
                mv = hsh.wmv
                bMate = False
                If hsh.svl > mConstValue.WIN_VALUE Then             '当前玩家胜利
                    hsh.svl -= nDistance        '提取时恢复杀棋步
                    bMate = True
                ElseIf hsh.svl < -mConstValue.WIN_VALUE Then        '对方胜利
                    hsh.svl += nDistance
                    bMate = True
                End If
    
                If hsh.ucDepth >= nDepth OrElse bMate Then
                    If hsh.ucFlag = mConstValue.HASHType.HASH_BETA Then             'BETA截断时,要超出边界。
                        Return IIf(hsh.svl >= vlBeta, hsh.svl, -mConstValue.MATE_VALUE)
                    ElseIf (hsh.ucFlag = mConstValue.HASHType.HASH_ALPHA) Then      'ALPHA截断时,要在边界之内。
                        Return IIf(hsh.svl <= vlAlpha, hsh.svl, -mConstValue.MATE_VALUE)
                    End If
                    Return hsh.svl
                End If
                Return -mConstValue.MATE_VALUE
            End SyncLock
        End Function
    
        ' 保存置换表项
        Public Shared Sub RecordHash(poskey As mPosKey, nFlag As Integer, vl As Integer, nDepth As Integer, nDistance As Integer, mv As Integer)
            SyncLock hstb
                Dim hsh As mPosZobItem = hstb(poskey.key And (mConstValue.HASH_SIZEOFPOS - 1))     '用and运算代替mod运算
                If hsh.ucDepth > nDepth Then Return '存储深度比现在深度小时,才更新。
                If hsh.ucDepth = nDepth AndAlso hsh.nDistance > nDistance Then Return '冲棋延伸局面计算量更大,所以保存更优先。
                hsh.ucFlag = nFlag
                hsh.ucDepth = nDepth
                hsh.nDistance = nDistance
                If vl > mConstValue.WIN_VALUE Then
                    hsh.svl = vl + nDistance        '存储时用杀棋步影响分值,从而使得覆盖过程可以存储到更快的杀棋。
                ElseIf vl < -mConstValue.WIN_VALUE Then
                    hsh.svl = vl - nDistance
                Else
                    hsh.svl = vl
                End If
                hsh.wmv = mv
                hsh.dwLock0 = poskey.dwLock0
                hsh.dwLock1 = poskey.dwLock1
                hstb(poskey.key And (mConstValue.HASH_SIZEOFPOS - 1)) = hsh
            End SyncLock
        End Sub
    
    End Class

    以上是局面和局面置换表。思路很清楚,值得注意的就是置换表保存时,同样深度下,由于冲棋延伸导致步数更多的局面实际上的深度要比以保存的深步数差个,为了方便代码中按同样深度保存了,实际上保存时应该重新计算深度(或许我们可以用深度与步数之和的大小关系作为覆盖依据),但即使现在的代码也可以提高很多命中率,而且显见这些提高的命中都是延伸若干步之后的结果,这为我们赢得了宝贵的时间。

    Imports System.Threading
    Public Class mPVSAlphaBeta
        Public pos As mPosition             '评价
        Public Event SearchEnd(a As Integer, b As Integer, c As Integer, d As Integer, e As Integer, vlbest As Integer, pline As mPVLine)
        '记数统计
        Public a, b, c, d, e As Integer
    
        '用局面类初始化
        Sub New(p As mPosition)
            pos = p
        End Sub
    
        '超出边界(Fail-Soft)的Alpha-Beta搜索过程。
        Public Function AlphaBeta(vlAlpha As Integer, vlBeta As Integer, nDepth As Integer, pLine As mPVLine, chk As Integer) As Integer
            b += 1
            d += 1
            Dim line As New mPVLine                 'pvs走法
            Dim nNewDepth As Integer                '搜索深度
            Dim nGenMove As Integer                 '子节点数
            Dim vl, vlBest, mvBest As Integer       '评价分值,最佳分值,最佳走法
            Dim InCheck As Integer                  '走一步棋时是否形成冲棋
            Dim mvs(224) As Byte                    '子节点走法缓存
            Dim mv As Integer                       '当前走法
            Dim nHashFlag As mConstValue.HASHType   '置换表标志
            Dim mvHash As Integer = -1              '哈希表走法
            Dim InCheckOnly As Boolean              '只生成冲棋走法,用于静态评价('''''''''''''''''''''''''''注释掉的语句就是静态评价)
            '最深走法步数
            If pos.nDistance > c Then
                c = pos.nDistance
            End If
            '1. 到达水平线,则返回局面评价
            If nDepth <= 0 Then
                '''''''''''''''''''''''''''If chk = 0 Then
                vl = pos.Evaluate
                Return vl
                '''''''''''''''''''''''''''Else
                '''''''''''''''''''''''''''InCheckOnly = True
                '''''''''''''''''''''''''''End If
            End If
    
            '2.到达极限深度,则返回局面评价
            If pos.nDistance = mConstValue.LIMIT_DEPTH Then Return pos.Evaluate()
    
            '3.查找置换表,应用剪裁
            vl = mZobristForPosition.ProbeHash(pos.poskey, vlAlpha, vlBeta, nDepth, pos.nDistance, mvHash)
            If vl > -mConstValue.MATE_VALUE Then
                a += 1
                pos.mvResult = mvHash
                Return vl
            End If
            '不尝试空步剪裁,因为空步剪裁适合于走任何一步都使局面更糟的时候,五子棋不会出现该情况。
            '4.初始化最佳值和最佳走法
            vlBest = -mConstValue.MATE_VALUE                    '这样可以知道,是否一个走法都没走过(杀棋)
            mvBest = -1                                         '这样可以知道,是否搜索到了Beta走法或PV走法,以便保存到历史表
            nGenMove = pos.NextGenerateMove(mvs, InCheck, InCheckOnly)       '当nGenMove为-1时,都是无解棋,直接截断。
            '5.逐一走这些走法,并进行递归
            For i As Integer = 0 To nGenMove
                mv = mvs(i)
                pos.SetPlayer(mv, pos.sdPlayer)
                '冲棋延伸
                nNewDepth = IIf(InCheck > 0 AndAlso (InCheck = chk OrElse InCheck > 2 OrElse chk > 2), nDepth, nDepth - 1)
                'PVS
                If vlBest = -mConstValue.MATE_VALUE Then
                    vl = -AlphaBeta(-vlBeta, -vlAlpha, nNewDepth, line, InCheck)
                Else
                    vl = -AlphaBeta(-vlAlpha - 1, -vlAlpha, nNewDepth, line, InCheck)        '空窗探测
                    If vl > vlAlpha AndAlso vl < vlBeta Then                        '<=alpha说明没有更好的棋,>=beta说明发生剪裁。
                        vl = -AlphaBeta(-vlBeta, -vlAlpha, nNewDepth, line, InCheck)
                    End If
                End If
                pos.SetPlayer(mvs(i), 2)
                '进行Alpha-Beta大小判断和截断
                If (vl > vlBest) Then                               '找到最佳值(但不能确定是Alpha、PV还是Beta走法)
                    vlBest = vl                                     '"vlBest"就是目前要返回的最佳值,可能超出Alpha-Beta边界
                    If (vl >= vlBeta) Then                          '找到一个Beta走法
                        nHashFlag = mConstValue.HASHType.HASH_BETA
                        mvBest = mv                                 'Beta走法要保存到历史表
                        Exit For                                    'Beta截断
                    End If
                    If (vl > vlAlpha) Then                          '找到一个PV走法
                        nHashFlag = mConstValue.HASHType.HASH_PV
                        mvBest = mv                                 'PV走法要保存到置换表
                        vlAlpha = vl                                '缩小Alpha-Beta边界
                        pLine.argmove(0) = mvBest                   '记录最佳走法路径
                        Array.Copy(line.argmove, 0, pLine.argmove, 1, line.cmove + 1)   '加入后续走法
                        pLine.cmove = line.cmove + 1                '更新走法总数
                    End If
                End If
            Next
            '6.所有走法都搜索完了,把最佳走法(不能是Alpha走法)保存到历史表,返回最佳值
            If vlBest = -mConstValue.MATE_VALUE Then
                '如果是杀棋,就根据杀棋步数给出评价
                Return pos.nDistance - mConstValue.MATE_VALUE
            End If
            '7.记录最佳招法
            If mvBest <> -1 Then
                '8.记录到置换表
                mZobristForPosition.RecordHash(pos.poskey, nHashFlag, vlBest, nDepth, pos.nDistance, mvBest)
                If pos.nDistance = 1 Then
                    'pos.mvResult = mvBest
                End If
            End If
            '9.返回最佳分值
            Return vlBest
        End Function
    
    End Class
    
    Public Class mPVLine
        Public cmove As Integer                                     '路线中着法的数量; 
        Public argmove(mConstValue.LIMIT_DEPTH - 1) As Byte         'PV路线上的着法列表
    End Class
    
    Public Class mSearch
        Public pos As mPosition             '评价
        Public pvLine As New mPVLine        '走法路线
        Public stopWatch As New Stopwatch   '计时器
        Public Event EndDepth(depth As Integer, nPos As Integer, bestMove As Integer, bestVal As Integer, lastTime As Integer, pvMine As String)
        Public Event EndAllDepth(lastTime As Integer, depth As Integer, nHashTable As Integer, nPos As Integer, maxDistance As Integer, NPS As Integer, bestVal As Integer)
        Public pvs As mPVSAlphaBeta
    
        Sub New(position As mPosition)
            pos = position
            pvs = New mPVSAlphaBeta(pos)
        End Sub
    
        '根节点搜索
        Function SearchRoot(nDepth As Integer)
            Dim line As New mPVLine                 'pvs走法
            Dim nGenMove As Integer                 '子节点数
            Dim vl, vlBest, mvBest As Integer       '评价分值,最佳分值,最佳走法
            Dim InCheck As Integer                  '走一步棋时是否形成冲棋
            Dim mvs(224) As Byte                    '子节点走法缓存
            Dim mv As Integer                       '当前走法
            Dim mvHash As Integer = -1
            pvLine.cmove = -1
            vlBest = -mConstValue.MATE_VALUE                    '这样可以知道,是否一个走法都没走过(杀棋)
            mvBest = -1                                         '这样可以知道,是否搜索到了Beta走法或PV走法,以便保存到历史表
            nGenMove = pos.NextGenerateMove(mvs, InCheck, False)       '当nGenMove为-1时,都是无解棋,直接截断。
    
            For i As Integer = 0 To nGenMove
                mv = mvs(i)
                pos.SetPlayer(mv, pos.sdPlayer)
                'PVS
                If vlBest = -mConstValue.MATE_VALUE Then
                    vl = -pvs.AlphaBeta(-mConstValue.MATE_VALUE, mConstValue.MATE_VALUE, nDepth - 1, line, InCheck)
                Else
                    vl = -pvs.AlphaBeta(-vlBest - 1, -vlBest, nDepth - 1, line, InCheck)          '空窗探测
                    If vl > vlBest Then                                             '<=alpha说明没有更好的棋,>=beta说明发生剪裁。
                        vl = -pvs.AlphaBeta(-mConstValue.MATE_VALUE, -vlBest, nDepth - 1, line, InCheck)
                    End If
                End If
                pos.SetPlayer(mvs(i), 2)
                '进行Alpha-Beta大小判断和截断
                If (vl > vlBest) Then                               '找到最佳值(但不能确定是Alpha、PV还是Beta走法)
                    vlBest = vl                                     '"vlBest"就是目前要返回的最佳值,可能超出Alpha-Beta边界
                    '找到一个PV走法
                    mvBest = mv                                     'PV走法要保存到置换表
                    pvLine.argmove(0) = mvBest                      '记录最佳走法路径
                    Array.Copy(line.argmove, 0, pvLine.argmove, 1, line.cmove)   '加入后续走法
                    pvLine.cmove = line.cmove + 1                   '更新走法总数
                End If
            Next
    
            '7.记录最佳招法
            If mvBest <> -1 Then
                '8.记录到置换表
                mZobristForPosition.RecordHash(pos.poskey, mConstValue.HASHType.HASH_PV, vlBest, nDepth, pos.nDistance, mvBest)
                pos.mvResult = mvBest
            End If
            '9.返回最佳分值
            Return vlBest
        End Function
    
        '===============================迭代加深===============================
        '迭代加深搜索过程
        Function SearchMain() As Integer
            Dim bctm As Integer '过去的总时间
            pvs.d = 0
            pvs.e = 0
            Dim i, t, vl As Integer
            '迭代加深过程
            For i = 1 To mConstValue.LIMIT_DEPTH - 1
                pvs.b = 0
                pos.StartUp()
                stopWatch.Restart()
                '最多招法记录
                'vl = AlphaBeta(-mConstValue.MATE_VALUE, mConstValue.MATE_VALUE, i, pvLine)
                vl = SearchRoot(i)
                stopWatch.Stop()
                t = stopWatch.ElapsedMilliseconds  '本次运算所用时间
                '若剩余时间小于上层搜索时间则退出搜索
                bctm += t   '至今所用全部时间
                RaiseEvent EndDepth(i, pvs.b, pos.mvResult, vl, t, PVLine2Str())
                '搜索到杀棋,就终止搜索
                If vl > mConstValue.WIN_VALUE Then  '计算机胜利
                    Exit For
                End If
                If vl < -mConstValue.WIN_VALUE Then '玩家胜利
                    Exit For
                End If
                If mConstValue.OutTime - bctm < t Then
                    Exit For
                End If
            Next
    
            RaiseEvent EndAllDepth(bctm, i, pvs.a, pvs.d, pvs.c, pvs.d * 1000 \ IIf(bctm = 0, 1, bctm), vl)
            Return pos.mvResult
        End Function
    
        '==============================================================================
        Function PVLine2Str() As String
            Dim tmp As String = "bestmove "
            Dim i As Integer
            For i = 0 To pvLine.cmove - 1
                If i = 1 Then tmp &= " ponder "
                If i = 2 Then tmp &= " moveline "
                tmp &= mConstValue.PosPoint2Str(pvLine.argmove(i) Mod 15) & (15 - (pvLine.argmove(i) \ 15)) & " " ' & "[" & (pvLine.argmove(i) & "]")
            Next
            Return tmp
        End Function
    
    End Class

    最后,就是分离了根节点的剪裁和迭代加深了。其实就是一个带有冲棋延伸、PVS的ALPHA-BETA剪裁。有什么不懂的可以留言,有什么指教的更要留言!

    好了,就这么多。然后传上源码。VS2010,.NET FRAMEWORK 4.0。

    。。。。找不到上传了呢。。。。。。。。。这里这里

    发一个最新版本。棋力远高于原来这个。点击下载

    全部文章和源码整理完成,以后更新也会在下面地址:

    http://www.vbdevelopers.org

    http://www.softos.org

     

  • 相关阅读:
    .net core webapi 前后端开发分离后的配置和部署
    403
    Visual Studio提示“无法启动IIS Express Web服务器”的解决方法
    一件能让人没脸见人的事
    过程 sp_addextendedproperty, 对象无效。不允许有扩展属性,或对象不存在。
    处理程序“ExtensionlessUrlHandler-Integrated-4.0”在其模块列表中有一个错误模块“ManagedPipelineHandler”
    阿里ECS配置MSSQL远程连接的坑
    .Net Webapi SignalR与微信小程序的交互
    Entity Framework 异常: 'OFFSET' 附近有语法错误。 在 FETCH 语句中选项 NEXT 的用法无效。
    postgresql 表继承
  • 原文地址:https://www.cnblogs.com/zcsor/p/2832820.html
Copyright © 2020-2023  润新知