「杂文」蒙特卡洛树搜索算法实现黑白棋 AI

写在前面

人工智能实验报告。

妈的我真的不会写实验报告,感觉一堆屁话

妈的下棋下不过爆搜,感觉大脑被白子雷普了

白子?白子……嘿嘿嘿我的白子

实验内容

黑白棋是一个经典的策略性游戏,一般棋子双面为黑白两色。因为行棋之时将对方棋子翻转,则变为己方棋子,又称翻转棋(Reversi)。游戏使用 \(8\times 8\) 的棋盘,通过相互翻转对方的棋子,最后以棋盘上谁的棋子多来判断胜负。

游戏规则:

  1. 棋局开始时,两个黑棋位于 E4 和 D5,两个白棋位于 D4 和 E5,如下图所示:

  2. 黑方先行,双方交替下棋。

  3. 一步合法的棋步包含:在一个空格新落下一个棋子,并且反转对手至少一个棋子;新落下的棋子与棋盘上已有的同色棋子间,对方被夹住的所有棋子都要反转过来。可以横向、纵向、沿对角线夹,夹住的位置上必须全部都是对手的棋子,不能有空格。

  4. 一步合法棋可以在数个(横向、纵向、对角线)方向上翻棋,任何被夹住的对手棋子都必须被翻转过来,棋手无权选择不去翻某个棋子。

  5. 若一方没有合法棋步,则该方这一轮只能弃权,由对手落子,

  6. 若一方至少有一步合法棋步可下,则该方必须落子,不得弃权。

  7. 棋局持续下去,直到棋盘填满或者双方都无合法棋步可下。

实验要求

  • 使用蒙特卡洛树搜索算法实现。
  • 使用 Python 语言。
  • 蒙特卡洛树搜索算法部分需要自行实现,不得使用现存的包、工具或接口。

实验环境

  • Python 3.9.12
  • Anaconda3-2024.02-1-Windows-x86_64
  • QT6 6.4.1
  • Qt Creator 8.0.2

实验原理

蒙特卡洛方法(Monte Carlo method)

蒙特卡罗方法(英语:Monte Carlo method),也称统计模拟方法,是1940年代中期提出的一种以概率统计理论为指导的数值计算方法。是指使用随机数(或更常见的伪随机数)来解决很多计算问题的方法。

通常蒙特卡罗方法可以粗略地分成两类:

  • 一类是所求解的问题本身具有内在的随机性,借助计算机的运算能力可以直接模拟这种随机的过程。
  • 另一种类型是所求解问题可转化为某种随机分布的特征数,通过随机抽样的方法以随机事件出现的频率估计其概率,或以抽样的数字特征估算随机变量的数字特征并将其作为问题的解。

在解决实际问题的时候应用蒙特卡罗方法主要有两部分工作:

  • 用蒙特卡罗方法模拟某一过程时,需要产生各种概率分布的随机变量。
  • 用统计方法把模型的数字特征估计出来,从而得到实际问题的数值解。

蒙特卡洛树搜索(英语:Monte Carlo tree search;简称:MCTS)是一种用于某些决策过程的启发式搜索算法,一个主要的使用例是电脑围棋程序。该算法将蒙特卡洛方法中的随机抽样方法用于游戏树搜索中,用于求解游戏中某给定局面的较优操作策略。

蒙特卡洛树搜索的每个循环包括如图所示的四个步骤:

  • 选择(Selection):从根节点 R 开始,连续向下按照某种策略选择子节点至叶子节点 L,令游戏树向最优的方向扩展,这是蒙特卡洛树搜索的精要所在。
  • 扩展(Expansion):除非任意一方的输赢使得游戏在叶节点 L 结束,否则根据操作创建一个或多个子节点并选取其中一个子节点 C。
  • 仿真(Simulation):再从节点 C 开始,用随机策略进行游戏。
  • 反向传播(Backpropagation):使用随机游戏的结果,更新从 C 到 R 的路径上的节点信息(访问次数与奖励值)。

在选择过程中,选择子结点的主要困难是:在较高平均胜率的移动后,在对深层次变型的利用和对少数模拟移动的探索,这二者中保持某种平衡。第一个在游戏中平衡利用与探索的公式被称为UCT(Upper Confidence Bounds to Trees,上限置信区间算法 ),基于 UCB1 公式,在选择子结点 C 尽量使下列表达式具有最大值:

\[\frac{w_c}{n_c} + \sqrt{\dfrac{2\ln n_u}{n_c}} \]

其中:

  • \(w_c\):子节点 C 的奖励值,在对其子节点进行树搜索的反向传播过程中更新。
  • \(n_c\):子节点 C 的访问次数,同上。
  • \(n_u\):父节点的访问次数。

目前蒙特卡洛树搜索的实现大多是基于 UCT 的一些变形,本次实验的蒙特卡洛树搜索算法基于上述经典 UCB1 公式进行。

实验思路

考虑使用蒙特卡洛树搜索算法解决黑白棋问题。

具体地,考虑定义棋盘类用于描述某时刻的棋盘状态与完成棋盘操作,定义节点类用于建立蒙特卡洛树搜索的结构,定义 AI 类用于完成蒙特卡洛树搜索算法并给出某棋盘状态下 AI 的最优解。上述三个类中的算法具体描述如下。

棋盘类

  • 使用 \(8\times 8\) 的二维列表描述棋盘状态,列表中每个位置与棋盘中的每个位置一一对应。
  • 支持对当前棋盘状态的如下操作:
    • 对某一颜色求解当前可落子位置:枚举每个位置检查落子是否合法。
    • 检查当前棋盘状态是否结束:检查两方是否均有合法落子位置。
    • 对当前棋盘状态进行落子操作:枚举落子位置周围八个方向检查是否成功使对方棋子翻转。
    • 求棋盘上两方棋子数量。

节点类

用于描述蒙特卡洛树搜索中的节点状态,与游戏时的棋盘状态一一对应。

则其中应有属性:

  • 用于描述当前游戏状态:当前棋盘状态,当前一步玩家的颜色。
  • 用于描述树的结构:父节点,子节点列表。
  • 用于描述当前状态的最优行为:当前一步行动。
  • 用于进行蒙特卡洛树搜索算法的参数:节点的访问次数,奖励值。

用于维护树的结构的方法:

  • 检查是否所有代表下一棋盘状态的子节点是否均被建立。
  • 建立子节点。

AI 类

对于某棋盘状态,基于节点类建立根节点 R,若干次运行上述实验原理中四个步骤求解较优操作策略。

四个步骤描述如下:

  • 选择:连续向下按照 UCB 公式选择较优子节点,以一定概率进行扩展,并继续选择较优子节点直至叶子节点 L。
  • 扩展:对于某节点,若它不为叶节点 L,则随机创建代表下一游戏状态的一个子节点。
  • 仿真:对于某通过选择过程得到的叶节点,则在当前游戏状态交替进行若干次随机合法落子操作,根据最终黑白子数量差得到奖励值,并进行反向传播。
  • 反向传播:使用随机游戏的结果,更新从 C 到 R 的路径上的节点信息(访问次数与奖励值)。

进行若干次操作后,对 R 按照 UCB 公式选择最优子节点,并将棋盘状态更新为子节点的状态。

代码结构

Infomation.py

用于存储全局常量与全局变量、全局函数。

  • 为 Board 类提供棋盘信息。
  • 为 Node 类、AI 类提供蒙特卡洛树搜索算法的参数与估值函数 ucb
  • 为 Widget 类提供游戏信息。

Board.py

棋盘类,包含属性:

  • block_num:棋盘上格子数量。
  • block_size:棋盘上格子大小。
  • margin:棋盘四周的空白宽度。

包含方法:

  • 初始化方法 __init__()
  • is_on_board(x, y):用于检查给定的坐标 \((x, y)\) 是否在棋盘范围内。
  • is_end():检查游戏是否结束,即棋盘上是否没有合法的落子点。
  • get_winner():获取游戏胜利者及胜利方与失败方的棋子数量之差。若游戏没有结束,返回 (-1, 0)。
  • is_legal_position(x, y, color):判断给定坐标 \((x, y)\) 是否是 color 的合法的落子点,即判断坐标是否在棋盘内,且该位置为空,且在该位置落子后至少翻转了一个对手的棋子。
  • GetInitialBoard():初始化棋盘。
  • CountColorNum(color):统计指定颜色的棋子数量。
  • GetLegalPosition(color):获取指定颜色的合法落子点。
  • PlaceColor(x, y, color, is_test=False):在指定位置落子,并翻转对方的棋子并返回翻转棋子的数量。若 is_test 参数为 True,则只进行测试,不实际进行落子和翻转操作。

Node.py

蒙特卡洛树搜索的节点类,包含属性:

  • board:当前棋盘状态。
  • color:当前一步玩家的颜色。
  • father:父节点。
  • action:当前一步行动。
  • visit_num:节点的访问次数。
  • reward:奖励值。
  • sons:子节点列表。

包含方法:

  • 初始化方法 __init__(board, color, father, action)。、
  • AddSon(board, action, color):向当前节点添加一个新的子节点。
  • fully_expand():检查当前节点是否完全扩展。

AI.py

AI 的操作类,在其中进行蒙特卡洛树搜索算法。包含属性:

  • max_try_time:每次进行 UCT 搜索时的最大模拟次数。
  • color:AI 的棋子颜色
  • scalar:UCB 公式中的参数。
  • select_probability:控制节点进行选择策略时一定概率选择不扩展的参数、
  • max_stimulate_times:在节点处随机模拟时的最大模拟步数。

包含方法:

  • 初始化方法 __init__(color)
  • Move(board):创建根节点,调用 UCT (Upper Confidence Bound applied to Trees)搜索方法来选择最佳的落子位置。
  • UCTSearch(root):多次调用 SelectPolicyStimulatePolicyBackup 方法来模拟最优策略并更新树节点,返回最优策略。
  • SelectPolicy(node):在当前节点进行策略的选择,根据 UCB 公式选择具有最高置信上限的子节点,若节点未完全扩展则扩展节点,同时有一定概率在可扩展时也不扩展。
  • Expand(node):扩展节点,在当前节点进行一次合法操作并创建新的子节点。
  • UCB(node, scalar):根据 UCB 公式计算置信上限值,并选择具有最高置信上限值的子节点作为下一步动作。
  • StimulatePolicy(node):在当前节点进行若干步随机模拟,根据模拟结果确定该节点的奖励值。
  • Backup(node, reward):完成模拟得到奖励值后将结果反向传播到根节点,更新每个节点的访问次数和奖励值。

Widget.py

定义了用户界面 Widget 类,实现了黑白棋的游戏逻辑。

主要包含方法:

  • mousePressEvent:在玩家回合响应玩家在棋盘上的点击动作,根据点击位置进行落子并更新棋盘状态。
  • GameStart():初始化棋盘并开始游戏。
  • Refresh():刷新棋盘状态。
  • GameInit():初始化游戏,创建棋盘对象并显示初始局面。
  • GameRemake():重新开始游戏。
  • AIMove():AI进行落子,调用 AI.move 选择最优的落子位置。
  • Judge():判断游戏是否结束,若未结束则检查当前落子方是否有合法落子位置,若无则轮到另一方。
  • GameEnd():游戏结束处理,弹出对话框显示胜负结果,并询问是否重新开始游戏。

运行结果


游戏初始状态截图

游戏进行截图

游戏结束截图

代码

Github 项目地址:https://github.com/Luckyblock233/Othello

写在最后

算法原理参考:

算法实现参考:

GUI 实现参考:

捉虫参考:

posted @ 2024-03-26 22:25  Luckyblock  阅读(908)  评论(0编辑  收藏  举报