把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

计算概论 A 大作业-简易版不围棋实验报告

核心算法

以蒙特卡洛方法为核心算法。solve.cpp 中实现了对于输入的一个棋盘状态,输出下一步下在哪个位置,用于支持电脑玩家(AI)对玩家对战。

具体地,对于每次输入的一个棋盘状态,我们新建一棵蒙特卡洛树。树上的每个节点都对应了一个棋盘状态,若节点 \(x\) 是节点 \(y\) 的父亲,则在 \(x\) 对应的棋盘上再下一步就可以得到 \(y\) 对应的棋盘。每个节点 \(x\) 还有两个参数, \(v_x\) 表示若干次更新的权值总和,\(n_x\) 表示更新的次数,在之后的 UCB 公式中会用到。

初始时树中仅有根节点,其状态即为输入的棋盘状态。随后进行若干次拓展操作(用 clock 函数控制总用时)。

每次拓展操作从根节点出发进行探索,若当前探索到的节点为终止节点(即棋盘上没有合法的位置可以继续下棋,胜负已分),则结束本次拓展操作。

否则,若当前节点先前未被探索过,就找出它所有可能的子节点(即下一步合法的棋能到达的状态),将这些节点加入蒙特卡洛树中,并从这些节点中随机选一个继续探索;若当前节点已经被探索过,则根据 UCB 公式选取一个最优的子节点继续探索。UCB 公式为

\[UCB_i=\bar{v}+c\cdot \sqrt{\frac{\log N}{n_i}} \]

该公式给出了子节点 \(i\) 的 UCB 值 \(UCB_i\) 的计算方法,其中 \(\bar v\) 表示该子节点的 \(\frac v n\)\(N\) 表示当前节点所有子节点 \(n\) 的总和,\(c\) 为常数因子,本程序中取 \(c=0.2\) 实现。子节点的 \(\bar v\) 越大,说明走到该子节点更可能获得较大的价值,而子节点的 \(n\) 越大,说明该子节点被探索的程度更充分,应考虑探索其他探索较不充分的子节点。UCB 公式给出了对这两个参数的一个均衡,我们在计算每个子节点的 UCB 值后,选取 UCB 值最大的子节点继续探索。

当拓展到终止节点时结束本次探索,用一个估价函数对这个最终状态进行评估:

\[val=t_1-t_2 \]

其中 \(t_1\) 表示最终状态下我方还能落子的位置数目,\(t_2\) 表示最终状态下对方还能落子的位置数目。在计算出最终状态的价值 \(val\) 后,回溯更新终止节点到根节点路径上每个节点的 \(v\)\(n\)

在所有的拓展操作结束后,从根节点的所有子节点中选出 \(\bar v\) 最大的子节点,算法给出的这一步棋即为从根节点到该子节点的一步棋。

主要功能以及实现方法

规则介绍

参考了 Botzone Wiki 上对 Nogo 游戏规则的介绍。

存盘,读盘,复盘

一个棋局的状态只需要记录当前棋盘上每个格子被黑子占据/被白子占据/未被占据,以及下一步应该下黑棋还是白棋。于是可以将一个棋局的状态用一个 9 * 9 的二维数组,以及一个 int 进行存储,并封装在结构体中。

利用一个结构体数组即可存储若干个相互独立的棋局,用于实现存盘与读盘。

玩家可以在轮到自己下棋时暂停此局游戏,并进行存档,之后可以继续此局游戏。

对于一局已经结束的游戏,玩家可以进行回放(复盘),程序会依次输出该局游戏中玩家与电脑下每一步后的棋盘状态。为了避免输出过快,利用 sleep 函数(windows 系统下为 Sleep 函数)控制输出速度。

棋盘与棋子的可视化

使用字符画画出棋盘,对于棋盘中每个格子,若未落子则输出空格,否则输出该位置上对应的棋子字符。

在棋盘的边缘上在棋盘边缘输出了每行每列对应的行号列号,便于玩家确定每个格子对应的坐标(行列编号)。

玩家落子

轮到玩家(人类)落子时,控制台出现提示落子的文字,玩家(人类)输入欲下棋位置的横纵坐标即可完成操作。

若输入不合法,则会提示玩家重新输入。

电脑或玩家每下一步后,都用 system("clear"); 清空控制台的输出,并重新输出新的棋盘状态。

提示

棋局下到中后盘时,棋盘中合法的落子位置会较少,玩家可以通过提示获得所有可落子的合法位置。

悔棋

在游戏结束前,轮到玩家下棋时,若不为第一步,则玩家可以选择悔棋,但每次只能悔一步棋。

胜负判定

solve.cpp 中实现了对于一个棋盘状态,判断当前行动方是否有合法的下棋位置,若无位置可下,则另一方获胜。双方每下一步后,都进行一次判断,当分出胜负时结束该局游戏,并重新回到初始界面。

posted @ 2022-11-07 16:58  jklover  阅读(322)  评论(0编辑  收藏  举报