Python开发AI应用-国际象棋应用

      AI 部分总述node

    AI在作出决策前通过三个不一样的步骤。首先,他找到全部规则容许的棋步(一般在开局时会有20-30种,随后会下降到几种)。其次,它生成一个棋步树用来随后决定最佳决策。虽然树的大小随深度指数增加,可是树的深度能够是任意的。假设每次决策有平均20个可选的棋步,那深度为1对应20棋步,深度为2对应400棋步,深度为3对应8000棋步。最后,它遍历这个树,采起x步后结果最佳的那个棋步,x是咱们选择的树的深度。后面的文章为了简单起见,我会假设树深为2。python

       生成棋步树算法

  棋步树是这个AI的核心。构成这个树的类是MoveNode.py文件中的MoveNode。他的初始化方法以下:数组

    

def __init__(self, move, children, parent) :
   self.move = move
   self.children = children
   self.parent = parent
   pointAdvantage = None
   depth = 1

  这个类有五个属性。首先是move,即它包含的棋步,它是个Move类,在这不是很重要,只须要知道它是一个告诉一个起子往哪走的棋步,能够吃什么子,等等。而后是children,它也是个MoveNode类。第三个属性是parent,因此经过它能够知道上一层有哪些MoveNode。pointAdvantage属性是AI用来决定这一棋步是好是坏用的。depth属性指明这一结点在第几层,也就是说该节点上面有多少节点。生成棋步树的代码以下:app

      

def generateMoveTree(self) :
    moveTree = []
    for move in self.board.getAllMovesLegal(self.side) :
        moveTree.append(MoveNode(move, [], None))
 
    for node in moveTree :
        self.board.makeMove(node.move)
        self.populateNodeChildren(node)
        self.board.undoLastMove()
    return moveTree

  变量moveTree一开始是个空list,随后它装入MoveNode类的实例。第一个循环后,它只是一个拥有没有父结点、子结点的MoveNode的数组,也就是一些根节点。第二个循环遍历moveTree,用populateNodeChildren函数给每一个节点添加子节点:dom

     

def populateNodeChildren(self, node) :
    node.pointAdvantage = self.board.getPointAdvantageOfSide(self.side)
    node.depth = node.getDepth()
    if node.depth == self.depth :
        return
 
    side = self.board.currentSide
 
    legalMoves = self.board.getAllMovesLegal(side)
    if not legalMoves :
        if self.board.isCheckmate() :
            node.move.checkmate = True
            return
        elif self.board.isStalemate() :
            node.move.stalemate = True
            node.pointAdvantage = 0
            return
 
    for move in legalMoves :
        node.children.append(MoveNode(move, [], node))
        self.board.makeMove(move)
        self.populateNodeChildren(node.children[-1])
        self.board.undoLastMove()

  这个函数是递归的,而且它有点难用图像表达出来。一开始给它传递了个MoveNode对象。这个MoveNode对象会有为1的深度,由于它没有父节点。咱们仍是假设这个AI被设定为深度为2。所以率先传给这个函数的结点会跳过第一个if语句。ide

      而后,决定出全部规则容许的棋步。不过这在这篇文章讨论的范围以外,若是你想看的话代码都在Github上。下一个if语句检查是否有符合规则的棋步。若是一个都没有,要么被将死了,要么和棋了。若是是被将死了,因为没有其余能够走的棋步,把node.move.checkmate属性设为True并return。和棋也是类似的,不过因为哪一方都没有优点,咱们把node.pointAdvantage设为0。函数

     若是不是将死或者和棋,那么legalMoves变量中的全部棋步都被加入当前结点的子节点中做为MoveNode,而后函数被调用来给这些子节点添加他们本身的MoveNode。优化

当结点的深度等于self.depth(这个例子中是2)时,什么也不作,当前节点的子节点保留为空数组。设计

    

       遍历树

    假设/咱们有了一个MoveNode的树,咱们须要遍历他,找到最佳棋步。这个逻辑有些微妙,须要花一点时间想明白它(在明白这是个很好的算法以前,我应该更多地去用Google)。因此我会尽量充分解释它。比方说这是咱们的棋步树:

    若是这个AI很笨,只有深度1,他会选择拿“象”吃“车”,致使它获得5分而且总优点为+7。而后下一步“兵”会吃掉它的“后”,如今优点从+7变为-2,由于它没有提早想到下一步。

       

    

    在假设它的深度为2。将会看到它用“后”吃“马”致使分数-4,移动“后”致使分数+1,“象”吃“车”致使分数-2。所以,他选择移动后。这是设计AI时的通用技巧,你能够在这找到更多资料(极小化极大算法)。

   因此咱们轮到AI时让它选择最佳棋步,而且假设AI的对手会选择对AI来讲最不利的棋步。下面展现这一点是如何实现的:

   

def getOptimalPointAdvantageForNode(self, node) :
    if node.children:
        for child in node.children :
            child.pointAdvantage = self.getOptimalPointAdvantageForNode(child)
 
        #If the depth is divisible by 2, it's a move for the AI's side, so return max
        if node.children[0].depth % 2 == 1 :
            return(max(node.children).pointAdvantage)
        else :
            return(min(node.children).pointAdvantage)
    else :
        return node.pointAdvantage

   这也是个递归函数,因此一眼很难看出它在干什么。有两种状况:当前结点有子节点或者没有子节点。假设棋步树正好是前面图中的样子(实际中每一个树枝上会有更多结点)。

第一种状况中,当前节点有子节点。拿第一步举例,Q吃掉N。它子节点的深度为2,因此2除2取余不是1。这意味着子节点包含对手的一步棋,因此返回最小步数(假设对手会走出对AI最不利的棋步)。

      该节点的子节点不会有他们本身的节点,由于咱们假设深度为2。所以,他们但会他们真实的分值(-4和+5)。他们中最小的是-4,因此第一步,Q吃N,被给为分值-4。

其余两步也重复这个步骤,移动“后”的分数给为+1,“象”吃“车”的分数给为-2。

       选择最佳棋步

    最难的部分已经完成了,如今这个AI要作的事就是从最高分值的棋步中作选择。

   

def bestMovesWithMoveTree(self, moveTree) :
    bestMoveNodes = []
    for moveNode in moveTree :
        moveNode.pointAdvantage = self.getOptimalPointAdvantageForNode(moveNode)
        if not bestMoveNodes :
            bestMoveNodes.append(moveNode)
        elif moveNode > bestMoveNodes[0] :
            bestMoveNodes = []
            bestMoveNodes.append(moveNode)
        elif moveNode == bestMoveNodes[0] :
            bestMoveNodes.append(moveNode)
 
    return [node.move for node in bestMoveNodes]

  此时有三种状况。若是变量bestMoveNodes为空,那么moveNode的值是多少,都添加到这个list中。若是moveNode的值高于bestMoveNodes的第一个元素,清空这个list而后添加该moveNode。若是moveNode的值是同样的,那么添加到list中。

      最后一步是从最佳棋步中随机选择一个(AI能被预测是很糟糕的)

   

bestMoves = self.bestMovesWithMoveTree(moveTree)
randomBestMove = random.choice(bestMoves)

  这就是全部的内容。AI生成一个树,用子节点填充到任意深度,遍历这个树找到每一个棋步的分值,而后随机选择最好的。这有各类能够优化的地方,剪枝,剃刀,静止搜索等等,可是但愿这篇文章很好地解释了基础的暴力算法的象棋AI是如何工做的。

    注意:部门内容参考于互联网

相关文章
相关标签/搜索