【五子棋实战】第2章 博弈树负值极大alpha-beta剪枝算法

news2024/9/22 11:41:26

【五子棋实战】第2章 博弈树极大极小值alpha-beta剪枝算法

  • 博弈树
  • 极大极小值搜索Minimax
  • 负值极大法Negamax
  • alpha-beta剪枝
  • 博弈树负值极大alpha-beta剪枝算法代码实现
    • ## 初始化输入、确定输出
    • ## 开始回溯
    • ## 判赢
    • ## 评估-计算分数
  • 总结
  • 继续学习下一篇实战!

  市面上比较常用的五子棋算法是博弈树极大极小值alpha-beta剪枝算法,该算法可以分成四个部分来讲解,它们是环环相扣的:博弈树 - 极大极小值搜索 - 负值极大法 - alpha&beta剪枝


博弈树

  博弈树(Game Tree)是博弈论中的一个概念,用于表示博弈过程中的各种可能走法和对应的结果。它是树结构,树的每个节点表示游戏的一个状态,每个节点的子节点表示在该状态下可能的下一步行动。

由于是树结构,在代码实现上会使用递归,再细分一下可以说是回溯法traceback。

  在博弈树中,根节点表示游戏的初始状态,而叶子节点则表示游戏结束的状态。通过遍历博弈树,可以找到最佳的行动策略或评估游戏的结果。

  对于两人对弈的游戏,博弈树的每一层交替表示不同的玩家的行动。在五子棋游戏中,轮到某一方行动时,会生成一层子节点,每个子节点代表下一步的落子位置。对于每个落子位置,继续生成下一层子节点,以此类推,直到游戏结束。

但是通常不可能一直类推到游戏结束,走3步的计算时间就很长很长了。

  举个例子,假设有一个2*2的五子棋棋盘,黑子先行,下在左上角,如下图:

请添加图片描述

  那么博弈树就如上图构建的这样。根节点为当前棋局,第1步是白子下棋,还剩下3个空位可以下;第2步又轮到黑子下棋,还剩2个空位;如此循环下去……

  我们发现对于2x2的棋盘,博弈树一共有3x2x1种路径。对于一个正常的15x15的棋盘,博弈树会有224层224!种路径。那么如何能确定哪一种路径是最优的呢?这就用到了极大极小值思想。


极大极小值搜索Minimax

  极大极小值思想是一种在博弈和人工智能领域中常用的方法。在博弈中,可以使用极大极小值搜索算法(如Minimax算法)来确定最佳的下一步行动。算法通过递归地模拟所有可能的游戏状态和对手的反应,然后选择最有利于自己的行动。

  在执行极大极小值搜索算法之前,我们需要给每个状态的棋盘进行打分,然后才能用极大极小值搜索算法来比较哪个状态的棋盘是优的。

  为棋盘打分 - 评分标准

  首先需要一个评分标准。对于五子棋来说,即如果出现了"空白白空空"这样的棋型我们要算多少分,如果出现了"白黑黑黑黑空"这样的棋型我们要算多少分。项目中设计的评分标准如下,并不区分黑白子,1为有子,0为空子:

# 棋型的评估分数
shape_score = [
    (50, (0, 1, 1, 0, 0)),
    (50, (0, 0, 1, 1, 0)),
    (200, (1, 1, 0, 1, 0)),
    (500, (0, 0, 1, 1, 1)),
    (500, (1, 1, 1, 0, 0)),
    (5000, (0, 1, 1, 1, 0)),
    (5000, (0, 1, 0, 1, 1, 0)),
    (5000, (0, 1, 1, 0, 1, 0)),
    (5000, (1, 1, 1, 0, 1)),
    (5000, (1, 1, 0, 1, 1)),
    (5000, (1, 0, 1, 1, 1)),
    (5000, (1, 1, 1, 1, 0)),
    (5000, (0, 1, 1, 1, 1)),
    (500000, (0, 1, 1, 1, 1, 0)),
    (99999999, (1, 1, 1, 1, 1))
]

这个评分标准的设计直接影响算法的效果,非常重要。而且,这里的评分标准也不是最优的,只是效果还不错,建议读者自行增加、修改去试试。

  为棋盘打分 - 如何累计分数

  累计分数也是一个大学问。

  本项目的做法是,对于每一个落白子的点A,去遍历A的横、竖、左斜、右斜的四个方向上是否有白子的形状落入评分标准,有的话就记录下最大分值和对应的形状。如果这个点A在四个方向中的某个方向已经被记录过,那么就不在纳入计算了。最后从记录的数组中找一下有没有相交的点,有的话得分加倍。

  黑子也是这样计算。

  最终棋盘的分数 = 我方颜色棋子总得分 - 敌方棋子总得分 * 权重。

所以其实白子、黑子都有参与到计算中来。权重体现了电脑的进攻性,可配置。

  极大极小值思想

  极大极小值思想的目的是为了找到博弈树最优的一条路径。

请添加图片描述

  极大极小值思想考虑的是在对手也采取最优策略的情况下,我们能够保证的最小收益或最大损失。

  在上图中,假设最后一层分数为1、2、3、4、5、6,那么它们的上一层是我方,我方取对自己有利的(取max),所以第三层还是1、2、3、4、5、6,第二层是敌方,他会取对我方最不利的分数(取min),所以是1、3、5,第一层是我方,取max=5。

  总结一下,当前黑子下完了,白子落子搜索到了第二层节点值为5的那个节点的落子坐标。

因为是回溯,所以树肯定是从下往上看。


负值极大法Negamax

  对于敌方而言,取得是最小。但是如果也让敌方取最大,只是最后加一个负号,那么不也相当于取最小吗??而且这样会简化我们的代码,使得我们的代码变得更少更优雅。于是就产生了负值极大法。

其实是使代码像天书一样抽象难看懂。。。。

  负值极大法(Negamax)是一种基于极小化搜索树的博弈算法。它基于一个基本的观察:对于一个玩家而言,最好的策略与对手的最差策略是等价的。因此,通过将对手的收益取负值,问题可以转化为一个极大化搜索问题。

  这两种算法本质上是相似的,只是表达方式不同。在Negamax算法中,通过将对手的收益取负值,将问题转化为一个极大化搜索问题;而在Minimax算法中,通过交替最大化和最小化的方式,在博弈树中搜索最佳决策。

请添加图片描述

  AI算法的厉害之处还在于你会往下递归几步,15x15的棋盘可以递归224步,有224!种路径,意味着你要从第224层往第一层回溯,估计只有超算才行了。

  通常我们递归3层就224x223x222=1108,9344个叶子节点了。于是,聪明的人对其进行了优化, 这就是alpha-beta剪枝搜索。


alpha-beta剪枝

  Alpha-beta剪枝是一种用于优化极大极小搜索(Minimax Search)算法的技术,它可以有效地减少搜索的节点数,从而提高搜索效率。Alpha-beta剪枝算法在Minimax搜索的基础上引入了两个参数:alpha和beta。

  在Minimax搜索过程中,每个玩家都追求最大化自己的收益或最小化对手的收益。在搜索的过程中,当某个节点的值超出了alpha到beta的范围时,可以通过剪枝操作来减少对该节点以及其子节点的搜索。

  具体来说,当搜索到一个节点时,会先搜索其子节点,并递归地进行下去。在递归返回之后,根据当前玩家是最大化还是最小化玩家,对alpha和beta进行更新。如果alpha的值大于等于beta的值,那么当前节点的搜索可以提前结束,不再搜索其余子节点,因为它们不会影响当前玩家的最佳选择。

  通过不断更新alpha和beta的值,可以在搜索的过程中动态地减少搜索的节点数,从而大幅度提高搜索效率。Alpha-beta剪枝算法的关键在于正确地维护alpha和beta的值,并且按照正确的顺序遍历子节点,以确保剪枝操作的正确性。

请添加图片描述
  特别重要 - 剪枝过程

  如上图,α为已知的最大值, β为已知的最小值, 因为还没搜索不知道是多少,保险起见,初始化为-∞ 和+∞。

  1 => 首先,深度优先遍历,会遍历到第四层第一个节点值为1,它的父节点属于Max层,那么把α置为1,说明这个父节点只接收>=1的值。

  2 => 接着回溯到了第二层第一个节点,由于每返回一层它们的α、β值是互换的,所以此时这个节点说它只接收<=1的值。

  3 => 接着遍历到第四层第二个节点值为2,它的父节点属于Max层,那么把α置为2,说明这个父节点只接收>=2的值。此时问题来了,第三层第二个节点>=2,第二层第一个节点<=1,产生冲突,把第三层第二个节点为根的子树剪掉,因为第三层第二个节点>=2表示我这个子树肯定是>=2了,但是它的父节点只要<=1的,那还要你干嘛?

  4 => 那么回溯到根节点,根节点现在变成>=1的了。

  5 => 接着遍历到第四层第三个节点值为3,它的父节点属于Max层,那么把α置为3,说明这个父节点只接收>=3的值。

  6 => 此时回溯到了第二层第二个节点,由于每返回一层它们的α、β值是互换的,所以此时这个节点说它只接收<=3的值。

  7 => 接着遍历到第四层第四个节点值为2,它的父节点属于Max层,那么把α置为2,说明这个父节点只接收>=2的值。而它的父节点是>=3,所以不剪枝,同时回溯,覆盖父节点的范围,变成<=2。

  8 => 那么又回溯到根节点,根节点之前是>=1的,现在被覆盖,变为>=2。

  9 => ……其他同理……

因为我图画的简陋,所以看不出来剪枝的妙处。但是可以想象一下上图被剪枝的部分,如果它们节点有很多,那么除了左子树,它们的右子树都不会再需要遍历了。

  值得注意的是,Alpha-beta剪枝算法适用于满足最优化原则的博弈问题,即一个玩家的收益等于另一个玩家的损失。它能够在保证找到最优解决方案的前提下,大大减少搜索的节点数,提高搜索效率。然而,如果搜索树的分支因子很高,或者搜索深度很大,仍然可能需要相当大的计算资源来完成搜索。因此,在实际应用中,仍然需要结合启发式评估函数等其他优化技术来进一步提高效率。


博弈树负值极大alpha-beta剪枝算法代码实现

## 初始化输入、确定输出

  首先明确算法的输入、输出,这其实是一个试错的过程,可能要到整体项目做的差不多才能确定,我是已经做完了,所以就直接贴出来了。

def ai(player, ratio, length, board, depth):
    """
    AI入口
    :param player: 当前玩家. 初始化ai与human的棋子列表.
    :param ratio: 进攻系数.
    :param length: 棋盘边长.
    :param board: 棋盘值. 1表示白子,-1表示黑子,0表示空.
    :param depth: 搜索深度.
    :return: 落子位置x, 落子位置y, 搜索次数.
    """
    # 设置全局变量
    global list_ai, list_human, list_ai_add_human, list_all, x, y, search_count, DEP, LEN, RAT
    # 初始化
    DEP, LEN, RAT = depth, length, ratio
    list_ai, list_human, list_ai_add_human, list_all = init_list(player, board)
    # 设置alpha和beta的初始值
    alpha = -99999999
    beta = 99999999
    # 回溯搜索
    backtrack(True, depth, alpha, beta)
    list_ai.append((x, y))
    return x, y, search_count, is_win(list_ai)

  该函数被命名为ai,也就是电脑的意思,接受以下参数:

  player: 当前玩家,用于初始化 AI 和人类玩家的棋子列表。

因为传入的只有棋盘值,棋盘值有1和-1,那电脑到底应该把1作为自己的棋子,还是-1呢?这就需要用到参数player,如果player == 'black',表示电脑是黑子,那么电脑会把-1作为自己的棋子,1作为敌方棋子。

  ratio: 进攻系数,可能是用于调整 AI 对进攻和防守的权衡。

进攻系数就是上面说的权重。

  length: 棋盘的边长。
  board: 表示棋盘状态的二维数组,其中1表示白子,-1表示黑子,0表示空。
  depth: 搜索的深度,决定了 AI 进行决策时回溯的层数。

搜索的深度就是所谓的“能看几步”,我电脑目前看3步就要算个40来秒,着实不太行了。

  该函数返回落子的位置(x, y)、搜索次数和是否获胜。

  在函数内部,使用global关键字声明一些全局变量,包括list_ailist_humanlist_ai_add_humanlist_allxysearch_countDEPLENRAT。分别表示ai的棋子、敌方的棋子、ai的棋子+敌方的棋子,所有的位置坐标、计算出来的x坐标、计算出来的y坐标、算法搜索次数(主要是debug用)、搜索深度、棋盘长度、进攻系数。

这里做成全局变量是因为就不要传参了,不然每个函数的形参都要写一大堆,挺麻烦的。

  接下来,将传入的参数初始化到对应的全局变量中。将 depth 赋值给 DEPlength 赋值给 LENratio 赋值给 RAT。然后调用 init_list 函数,使用 playerboard 参数来初始化 list_ailist_humanlist_ai_add_humanlist_all 这些列表。

  设置初始的alphabeta值,用于进行Alpha-Beta剪枝。

  调用 backtrack 函数进行回溯搜索,传入参数 True 表示当前是 AI 的回合,搜索的深度为 depth,并使用 alphabeta 进行 Alpha-Beta 剪枝。搜索完成后,将得到的落子位置 (x, y) 添加到 list_ai 列表中,然后返回 (x, y)search_count(搜索次数)和调用 is_win 函数判断 list_ai 是否包含获胜的棋型。

## 开始回溯

def backtrack(is_me, depth, alpha, beta):
    """
    博弈树极大极小值alpha-beta剪枝搜索
    :param is_me: 当前是不是轮到自己下棋.
    :param depth: 回溯深度.
    :param alpha: 
    :param beta: 
    :return: 
    """
    # 约束条件:已经有一方获胜 或者 搜索深度为0
    if is_win(list_ai) or is_win(list_human) or depth == 0:
        return evaluation(is_me)

    # 生成当前局面下所有的候选步
    blank_list = list(set(list_all).difference(set(list_ai_add_human)))
    # 启发式搜索, 优先剪枝
    order(blank_list)  # 搜索顺序排序  提高剪枝效率

    # 遍历每一个候选步
    for next_step in blank_list:

        global search_count
        search_count += 1

        # 如果要评估的位置没有相邻的子, 则不去评估  减少计算
        if not has_neightnor(next_step):
            continue

        if is_me:
            list_ai.append(next_step)
        else:
            list_human.append(next_step)

        list_ai_add_human.append(next_step)

        value = -backtrack(not is_me, depth - 1, -beta, -alpha)

        if is_me:
            list_ai.remove(next_step)
        else:
            list_human.remove(next_step)

        list_ai_add_human.remove(next_step)

        if value > alpha:
            if depth == DEP:
                global x, y
                x = next_step[0]
                y = next_step[1]
            # alpha + beta剪枝点
            if value >= beta:
                return beta
            alpha = value
    return alpha

  上面的代码是用博弈树极大极小值算法和alpha-beta剪枝搜索实现五子棋最优点搜索的函数。

  函数名为backtrack,接受四个参数:

  is_me:表示当前轮到的是自己还是对手下棋,是一个布尔值。
  depth:回溯的深度,即搜索树的层数。
  alpha:alpha剪枝的初始值,表示极大值。
  beta:beta剪枝的初始值,表示极小值。

  函数的作用是在博弈树中进行搜索,并返回评估值。

  函数的执行流程如下:

  1. 约束条件检查:如果已经有一方获胜或者搜索深度为0,则返回当前局面的评估值,表示当前局面的好坏程度。

  2. 生成当前局面下所有的候选步:通过计算当前局面中空白位置和已下棋位置的差集,得到所有可下棋的位置。

  3. 启发式搜索:对候选步进行排序,以提高剪枝效率。还对周围没有棋子的点位进行搜索优化,没有的话就不去遍历它。

  4. 遍历每一个候选步:

    对于每个候选步,将其添加到当前下棋方的位置列表中。

    调用递归函数backtrack,以更新搜索树,并返回一个评估值。

    通过alpha-beta剪枝进行优化:

      如果当前轮到自己下棋(is_me为True),更新alpha值为评估值value和alpha的较大值。
      如果轮到对手下棋(is_me为False),更新beta值为评估值value和beta的较小值。
      如果评估值value大于alpha,则更新alpha为value
      如果depth等于最大搜索深度(DEP),记录当前位置xy
      如果评估值value大于等于beta,则进行剪枝,直接返回beta。

  5. 返回alpha作为当前局面的评估值。

## 判赢

  判赢函数有2个用法:一个是在回溯里面作为约束条件,还有一个是在得到计算出的落子之后,判断这样下棋有没有赢。

def is_win(L):
    """
    判断输赢
    :param L: 电脑或玩家的落子列表
    :return: True or False.
    """
    def check_five_in_a_row(start_row, start_col, row_delta, col_delta):
        for i in range(5):
            if (start_row + i * row_delta, start_col + i * col_delta) not in L:
                return False
        return True

    for m in range(LEN):
        for n in range(LEN):
            # 判断横向是否有五子连珠
            if n < LEN - 4 and check_five_in_a_row(m, n, 0, 1):
                return True
            # 判断纵向是否有五子连珠
            if m < LEN - 4 and check_five_in_a_row(m, n, 1, 0):
                return True
            # 判断右上斜向是否有五子连珠
            if m < LEN - 4 and n < LEN - 4 and check_five_in_a_row(m, n, 1, 1):
                return True
            # 判断右下斜向是否有五子连珠
            if m < LEN - 4 and n > 3 and check_five_in_a_row(m, n, 1, -1):
                return True
    return False

  判断输赢函数is_win,接受一个落子列表L作为参数,并返回一个布尔值表示是否有一方获胜。

  函数的执行流程如下:

  1. 定义了一个内部函数check_five_in_a_row,用于检查指定起始位置和方向上是否存在五子连珠。

  · 函数接受四个参数:起始行start_row、起始列start_col、行的增量row_delta和列的增量col_delta

  · 使用循环遍历五个位置,检查每个位置是否存在于落子列表L中,如果有任何一个位置不在列表中,则返回False。

  · 如果所有位置都在列表中,则返回True,表示存在五子连珠。

  2. 使用两层嵌套的循环遍历棋盘上的每个位置,对于每个位置,分别进行以下判断:

   - 判断横向是否有五子连珠:如果当前位置的列小于LEN-4(棋盘边界判断)且调用check_five_in_a_row函数返回True,则表示存在横向五子连珠,返回True。

   - 判断纵向是否有五子连珠:如果当前位置的行小于LEN-4且调用check_five_in_a_row函数返回True,则表示存在纵向五子连珠,返回True。

   - 判断右上斜向是否有五子连珠:如果当前位置的行小于LEN-4且列小于LEN-4且调用check_five_in_a_row函数返回True,则表示存在右上斜向五子连珠,返回True。

   - 判断右下斜向是否有五子连珠:如果当前位置的行小于LEN-4且列大于3且调用check_five_in_a_row函数返回True,则表示存在右下斜向五子连珠,返回True。

  3. 如果在遍历完所有位置后仍未返回True,则表示不存在五子连珠,返回False。

## 评估-计算分数

  计算分数分2个函数,一个是计算整体的分数evaluation,另外一个是针对某个点计算某个方向上的分数cal_score

# 评估函数
def evaluation(is_me):
    if is_me:
        my_list = list_ai
        enemy_list = list_human
    else:
        my_list = list_human
        enemy_list = list_ai

    # 算自己的得分
    score_all_arr = []  # 得分形状的位置 用于计算如果有相交 得分翻倍
    my_score = 0
    for pt in my_list:
        m = pt[0]
        n = pt[1]
        my_score += cal_score(m, n, 0, 1, enemy_list, my_list, score_all_arr)
        my_score += cal_score(m, n, 1, 0, enemy_list, my_list, score_all_arr)
        my_score += cal_score(m, n, 1, 1, enemy_list, my_list, score_all_arr)
        my_score += cal_score(m, n, -1, 1, enemy_list, my_list, score_all_arr)

    #  算敌人的得分, 并减去
    score_all_arr_enemy = []
    enemy_score = 0
    for pt in enemy_list:
        m = pt[0]
        n = pt[1]
        enemy_score += cal_score(m, n, 0, 1, my_list, enemy_list, score_all_arr_enemy)
        enemy_score += cal_score(m, n, 1, 0, my_list, enemy_list, score_all_arr_enemy)
        enemy_score += cal_score(m, n, 1, 1, my_list, enemy_list, score_all_arr_enemy)
        enemy_score += cal_score(m, n, -1, 1, my_list, enemy_list, score_all_arr_enemy)

    total_score = my_score - enemy_score * RAT * 0.1

    return total_score

  上述代码是一个评估函数evaluation,用于评估当前局面的得分。函数根据当前轮到的是自己还是对手,计算出相应的得分。

  函数的执行流程如下:

  1. 根据参数is_me判断当前轮到的是自己还是对手,将对应的落子列表赋值给my_listenemy_list

  2. 初始化变量:

  score_all_arr:用于存储得分形状的位置,用于计算如果有相交则得分翻倍。
  my_score:自己的得分,初始值为0。

  3. 遍历自己的落子列表my_list,对于每个位置(m, n),分别进行以下操作:

  调用cal_score函数计算在水平、垂直、右斜和左斜四个方向上的得分,并累加到my_score中。
  在计算得分的过程中,使用enemy_listmy_list作为参数,以判断对手的棋子是否存在,同时将得分形状的位置存储到score_all_arr中。

  4. 初始化变量:

  score_all_arr_enemy:用于存储对手的得分形状的位置。
  enemy_score:对手的得分,初始值为0。

  5. 遍历对手的落子列表enemy_list,对于每个位置(m, n),分别进行以下操作:

  调用cal_score函数计算对手在水平、垂直、右斜和左斜四个方向上的得分,并累加到enemy_score中。
  在计算得分的过程中,使用my_listenemy_list作为参数,以判断自己的棋子是否存在,同时将得分形状的位置存储到score_all_arr_enemy中。

  6. 计算总得分:

  将自己的得分my_score减去对手的得分enemy_score乘以一个权重因子RAT和0.1的结果,得到总得分total_score

  7. 返回总得分total_score作为当前局面的评估值。

# 每个方向上的分值计算
def cal_score(m, n, x_decrict, y_derice, enemy_list, my_list, score_all_arr):
    add_score = 0  # 加分项
    # 在一个方向上, 只取最大的得分项
    max_score_shape = (0, None)

    # 如果此方向上,该点已经有得分形状,不重复计算
    for item in score_all_arr:
        for pt in item[1]:
            if m == pt[0] and n == pt[1] and x_decrict == item[2][0] and y_derice == item[2][1]:
                return 0

    # 在落子点方向上循环查找得分形状
    for offset in range(-5, 1):
        pos = []
        for i in range(0, 6):
            if (m + (i + offset) * x_decrict, n + (i + offset) * y_derice) in enemy_list:
                pos.append(2)
            elif (m + (i + offset) * x_decrict, n + (i + offset) * y_derice) in my_list:
                pos.append(1)
            else:
                pos.append(0)
        tmp_shap5 = (pos[0], pos[1], pos[2], pos[3], pos[4])
        tmp_shap6 = (pos[0], pos[1], pos[2], pos[3], pos[4], pos[5])

        for (score, shape) in shape_score:
            # 命中一个得分形状
            if tmp_shap5 == shape or tmp_shap6 == shape:
                if score > max_score_shape[0]:
                    max_score_shape = (score, ((m + (0+offset) * x_decrict, n + (0+offset) * y_derice),
                                               (m + (1+offset) * x_decrict, n + (1+offset) * y_derice),
                                               (m + (2+offset) * x_decrict, n + (2+offset) * y_derice),
                                               (m + (3+offset) * x_decrict, n + (3+offset) * y_derice),
                                               (m + (4+offset) * x_decrict, n + (4+offset) * y_derice)), (x_decrict, y_derice))

    # 计算两个形状相交, 如两个3活 相交, 得分增加 一个子的除外
    if max_score_shape[1] is not None:
        for item in score_all_arr:
            for pt1 in item[1]:
                for pt2 in max_score_shape[1]:
                    if pt1 == pt2 and max_score_shape[0] > 10 and item[0] > 10:
                        add_score += item[0] + max_score_shape[0]

        score_all_arr.append(max_score_shape)

    return add_score + max_score_shape[0]

  上述代码是计算在指定方向上的得分函数cal_score,用于评估棋局中某个位置在特定方向上的得分情况。

  函数的执行流程如下:

  1. 初始化变量:
   - add_score:加分项,用于记录在当前方向上的额外得分。
   - max_score_shape:最大得分形状,初始值为(0, None),用于记录在当前方向上的最高得分形状及其分值。

  2. 检查当前方向上是否已经有得分形状,如果有,则不进行重复计算,直接返回0。

  3. 在落子点的方向上循环遍历,查找可能的得分形状。遍历范围为从偏移-5到1,共6个位置。

  4. 对于每个位置,判断该位置在落子列表中的状态:
   - 如果在对手的落子列表中,将状态设置为2。
   - 如果在自己的落子列表中,将状态设置为1。
   - 如果为空位,将状态设置为0。

  5. 根据当前位置及其相邻位置的状态,形成长度为5或6的形状。

  6. 遍历预定义的得分形状和对应的分值:
   - 如果当前形状与预定义的形状匹配,命中得分形状。
   - 如果当前得分高于max_score_shape中记录的最高得分,则更新max_score_shape为当前得分形状。

  7. 判断两个形状是否相交:
   - 如果max_score_shape不为None,表示存在最高得分形状。
   - 遍历已记录的得分形状,检查是否与最高得分形状相交:
   - 如果相交且两个形状的分值都大于10,则将相交的得分形状的分值加到add_score中。

  8. 将最高得分形状记录到score_all_arr中。

  9. 返回加分项add_score与最高得分形状的分值之和作为当前方向上的总得分。

  通过遍历落子点的特定方向上的位置,并判断形成的形状是否与预定义的得分形状匹配,从而计算出该方向上的得分情况。同时,如果存在相交的得分形状,则额外增加得分。最终返回当前方向上的总得分。


总结

  ai 调用 backtrace进行回溯。

   backtrace里面有约束条件:判赢is_win、depth是否为0。

  如果满足约束条件,那么计算总棋局分数evaluationevaluation会调用cal_scorecal_score计算某个点在某个方向的分数。

  如果不满足约束条件,做一下启发(剪枝),然后接着递归。

  最后返回四个参数:x、y坐标,搜索次数、是否赢了。


继续学习下一篇实战!

  【五子棋实战】第3章 算法包装成第三方接口

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/675001.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

注解和异常的详细笔记

注解的理解 注解(Annotation)也被称为元数据(Metadata)&#xff0c;用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。和注释一样&#xff0c;注解不影响程序逻辑&#xff0c;但注解可以被编译或运行&#xff0c;相当于嵌入在代码中的补充信息。在 JavaSE 中&am…

OpenMMLab-AI实战营第二期——相关1. COCO数据集格式和pycocotools使用(目标检测方向)

文章目录 1. COCO数据集1.1 COCO数据集介绍1.2 COCO数据集格式1.2.1 常见目标检测数据格式1.2.2 COCO数据集文件结构及标注文件说明1.2.3 COCO的evaluation指标 1.3 其他 2. pycocotools2.1 pycocotools简介和安装2.2 基本使用 3. 图像的EXIF使用3.1 图像的EXIF3.1.1 基本介绍3…

车载以太网MACsec

车载以太网MACsec 1.概述 MACsec&#xff08;Media Access Control Security&#xff09;定义了基于 IEEE 802 局域网络的数据安全通信的方法。MACsec 可为用户提供安全的 MAC 层数据发送和接收服务&#xff0c;包括用户数据加密&#xff08;Confidentiality&#xff09;、数…

新浪股票接口获取历史数据

这两天做了一个调用新浪股票接口获取实时以及历史股票数据的应用&#xff0c;因为新浪没有公开关于其接口的官方文档&#xff0c;所以通过各种百度差了很多关于新浪股票接口的使用&#xff0c;不过大家基本都是转载或者直接复制&#xff0c;对于实时数据的获取讲的很详细&#…

Linux【系统学习】(shell篇)

第 1 章 Shell 概述 1&#xff09;Linux 提供的 Shell 解析器有 Ubuntu 使用的是dash 2&#xff09;bash 和 sh 的关系 3&#xff09;Centos 默认的解析器是 bash 第 2 章 Shell 脚本入门 1&#xff09;脚本格式 &#xff08;结尾不是必须以 .sh 结尾&#xff0c;只是为了区…

《网络安全0-100》VPN 讲解

1、前言 VPN虽好可不要贪玩哦 2、VPN的概念和结构 VPN:虚拟专用网(virtual personal network)是利用internet等公共网络的基础设施&#xff0c;通过隧道技术&#xff0c;为用户提供的专用网络具有相同通信功能的安全数据通道。 ‘虚拟’是指用户无需建立各逻辑上的专用物理线…

​AVS3支持下的8K内容生产和传输应用实践

AVS标准经过20年发展&#xff0c;到AVS3这一代实现了国产标准从跟跑到领跑的突破。博雅睿视团队深度参与AVS3标准制定&#xff0c;开发的8K AVS3实时、离线编解码设备有力支持了我国8K超高清视频产业的落地和推广。 本次分享将会分为三个部分&#xff0c;第一部分重点介绍AVS标…

MusicGen配乐工具开源,教你怎么给抖音短视频配乐,助你涨粉1000!

大家好&#xff0c;我是千与千寻&#xff0c;好久不见了&#xff0c;很多粉丝私信我说&#xff0c;千寻哥这是去哪了&#xff1f;难道被野外捕捉了。 哈哈哈&#xff0c;当然不是了&#xff0c;千寻依然在学习ChatGPT的道路上和大家一起学习&#xff0c;一起搞钱&#xff01; 但…

TC8:TCP_MSS_OPTIONS_05-09

TCP_MSS_OPTIONS_05: Illegal option length for MSS in a SYN-ACK segment 目的 TCP能够处理SYN+ACK报文段中MSS选项的长度非法,而不会崩溃 可以正常地响应,就是不崩溃,这里指的应该是DUT发送ACK 测试步骤 Tester:让DUT侧的应用程序发起主动open调用DUT:发送SYNTester:…

C++类和对象--构造函数和析构函数

0.前言 在我们写某些需要动态开辟内存空间的函数时候&#xff0c;会经常忘记初始化、销毁&#xff0c;而且有时候程序返回的情况很多&#xff0c;那么销毁函数写起来就会很繁琐&#xff0c;那么有没有什么办法解决这个问题呢&#xff1f;答案是&#xff1a;当然有&#xff01;在…

Java类 继承

Java是一种面向对象的编程语言&#xff0c;类是Java中最基本的编程单元。一个类可以包含变量、方法和构造函数等。类的定义需要使用关键字class&#xff0c;例如&#xff1a; public class Person { private String name; private int age; public Person(St…

文心一言 VS 讯飞星火 VS chatgpt (44)-- 算法导论6.1 1题

一、在高度为h的堆中&#xff0c;元素个数最多和最少分别是多少? 文心一言&#xff1a; 在高度为 h 的二叉搜索树&#xff08;BST&#xff09;中&#xff0c;元素个数最多和最少的情况分别如下&#xff1a; 1.最多情况&#xff1a;当每个节点都包含两个子节点时&#xff0c…

CSS3-盒子模型-边距问题

1 清除默认内外边距 场景&#xff1a;浏览器会默认给部分标签设置默认的margin和padding&#xff0c;但一般在项目开始前需要先清除这些标签默认的margin和padding&#xff0c;后续自己设置 比如&#xff1a;body标签默认有margin&#xff1a;8px、p标签默…

一句提示词生成整个代码库——Gpt Engineer神级项目开源(附演示视频)

近日&#xff0c;一个名为Gpt Engineer神级项目开源&#xff0c;并迅速火爆全网。 短短几天内&#xff0c;该项目已经得到了25K的星星。 使用GPT-engineer进行人工智能软件开发&#xff0c;可以改变软件开发的未来。 在软件开发领域&#xff0c;一场巨大的革命正在开始。这一转…

【计算机视觉 | 目标检测】arxiv 计算机视觉关于目标检测的学术速递(6月 22 日论文合集)

文章目录 一、检测相关(9篇)1.1 Wildfire Detection Via Transfer Learning: A Survey1.2 Polygon Detection for Room Layout Estimation using Heterogeneous Graphs and Wireframes1.3 Exploiting Multimodal Synthetic Data for Egocentric Human-Object Interaction Detec…

前端中的相关概念

谁道人生无再少&#xff0c; 门前流水尚能西。 桃花落尽胭脂透&#xff0c; 庭院无声五更鸡。 —— 杜甫《端午节》 HTML中class属性 HTML中class属性是一种用于为元素定义样式和标识的属性&#xff0c;以下是class属性的几种常见用法实例&#xff0c;包括标识元素、定义样…

利用OpenCV计算条形物体的长度

0、前言 在图像处理中&#xff0c;我们可能会遇到求一个线条长度的场景&#xff0c;比如&#xff0c;现在有一条裂缝&#xff0c;需要求其长度&#xff0c;或者有一个长条形的零件需要知道其长度。 本文利用OpenCV和skimage两个库&#xff0c;提供了一个解决方案。 1、解决步…

贪心法与动态规划的对比分析

高级算法设计课程论文 题 目&#xff1a;贪心法与动态规划的对比分析 作者姓名&#xff1a; 作者学号&#xff1a; 专业班级&#xff1a; 提交时间&#xff1a; 2023/6/3 目 录 1 引言 1 2 分析过程 2 2.1多段图的最短路径问题 2 2.2最小生成树问题 4 3动态规划与贪心法的对…

【动态规划算法练习】day3

文章目录 一、931. 下降路径最小和1.题目简介2.解题思路3.代码4.运行结果 二、64. 最小路径和1.题目简介2.解题思路3.代码4.运行结果 三、面试题 17.16. 按摩师1.题目简介2.解题思路3.代码4.运行结果 总结 一、931. 下降路径最小和 1.题目简介 931. 下降路径最小和 题目描述&…

浅析 GeoServer CVE-2023-25157 SQL注入

原创稿件征集 邮箱&#xff1a;eduantvsion.com QQ&#xff1a;3200599554 黑客与极客相关&#xff0c;互联网安全领域里 的热点话题 漏洞、技术相关的调查或分析 稿件通过并发布还能收获 200-800元不等的稿酬 更多详情&#xff0c;点我查看&#xff01; 简介 GeoServer是一个开…