首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >带有快速随机推出的六角板游戏(OpenAI健身房框架)

带有快速随机推出的六角板游戏(OpenAI健身房框架)
EN

Code Review用户
提问于 2020-06-30 07:49:15
回答 2查看 145关注 0票数 3

我使用棋盘六角健身房框架实现了OpenAI,目的是构建一个机器人/人工智能播放器,它可以通过自我游戏和专家迭代来学习(详细信息注释:不是我的论文,我只是在复制它)。

初始代理使用蒙特卡罗树搜索(MCTS),我将比较自己与它来评估不同机器人的强度。MCTS涉及到用随机动作(称为展示)来模拟游戏,这是做了很多(在实际游戏中每一步玩超过1,000个游戏),所以这个推出速度对我很重要。事实上,当我分析我的代码时,瓶颈被说成是推出,更具体地说,测试游戏是否已经结束。

目前,我检查游戏是否使用以下机制完成(我确信它有一个名称,但我不知道):

  1. 在棋盘上加装1排/列,并在棋盘的西侧/东侧(白色/蓝色)或北面/南面(黑色/红色)(游戏开始时缓存)放置石块。
  2. 查找当前播放器的所有连接区域(从上一轮缓存)
  3. 把石头放在船上
  4. 检查石头的邻域和(a)开始新的区域,如果没有连接,(b)添加到区域指数最低的区域
  5. 如果附近有多个区域,则将它们与索引最低的区域合并。

我为北/西(黑/白)填充的石头分配索引1,然后通过检查东南角可以有效地测试游戏是否结束。如果它有区域索引1,它会连接到对方,游戏已经完成。

游戏的完整代码可在GitHub上使用,同时还可以执行随机展开的MWE。这不是一个大回购(可能500条线)。关键函数是这个

代码语言:javascript
运行
复制
    def flood_fill(self, position):
        regions = self.regions[self.active_player]

        current_position = (position[0] + 1, position[1] + 1)
        low_x = current_position[1] - 1
        high_x = current_position[1] + 2
        low_y = current_position[0] - 1
        high_y = current_position[0] + 2
        neighbourhood = regions[low_y:high_y, low_x:high_x].copy()
        neighbourhood[0, 0] = 0
        neighbourhood[2, 2] = 0
        adjacent_regions = sorted(set(neighbourhood.flatten().tolist()))
        adjacent_regions.pop(0)

        if len(adjacent_regions) == 0:
            regions[tuple(current_position)] = self.region_counter[self.active_player]
            self.region_counter[self.active_player] += 1
        else:
            new_region_label = adjacent_regions.pop(0)
            regions[tuple(current_position)] = new_region_label
            for label in adjacent_regions:
                regions[regions == label] = new_region_label

最贵的是adjacent_regions = sorted(set(neighbourhood.flatten().tolist()))。我想知道这是否可以用一种更好的方式实现,要么使用不同的算法,要么将代码矢量化更多,更智能的缓存,……

当然,我对代码的任何其他评论也很满意。

免责声明:我在OpenAI健身房回购中的一个旧提交中找到了一个基本的十六进制实现,我用它作为工作的基础。大多数代码都已更改,但有些代码(例如,呈现函数)不是我自己编写的。

EN

回答 2

Code Review用户

发布于 2020-06-30 08:57:21

当单独读取此函数时,没有任何周围的代码,我想知道+ 1的初始position来自哪里。在我看来,这是一种千篇一律的错误。我不知道这是否真的是个窃听器,只是有点可疑。

tuple()的调用看起来是多余的,因为current_position已经是一个元组。IDE没有警告过这样的事情吗?

position这个词是个坏名字,因为它是不明确的。它可以是(x, y)元组,也可以是完整的(board, player_to_move)元组,就像“在这个位置上,红色应该辞职”这样的句子。更好的名字应该是last_moveprev_move

你使用元组有什么好的理由吗?有两个变量xy将使代码变得非常清晰。这些变量名足够短,因此不再需要low_x和相关变量。

你需要打电话到tolist()吗?

而不是生成一个二维矩阵,它可能会更有效,如果你只是把6个相邻的区域显式和单独。这样,您还可以摆脱pop(0)。不过,我不知道在Python中这是否更快。

票数 2
EN

Code Review用户

发布于 2020-06-30 18:38:06

如果没有配置文件编号,我就不能建议对函数的输入进行假设的更改。例如,如果您知道‘游戏结束时的检查’失败了,那么您只能在玩家在每一行中有一块,在每列中有一个部分时运行该检查。我也会挑选一些小的东西,因为我不知道这个函数的哪些特定部分太慢了。从某种意义上说,下面的更改对您的代码有些不可知论,可能对所有这些都没有多大帮助。

作为个人偏好,我不喜欢使用索引自由使用的代码。我发现它常常比需要的更难读。

代码语言:javascript
运行
复制
current_position = (position[0] + 1, position[1] + 1)
low_x = current_position[1] - 1
high_x = current_position[1] + 2
low_y = current_position[0] - 1
high_y = current_position[0] + 2

这里有一些不必要的加减。你可以简化一下。

代码语言:javascript
运行
复制
low_x = current_position[1] - 1
low_x = position[1] + 1 - 1  # Replace current_position[1] with its definition: position[1] + 1
low_x = position[1]

这里的其他变量也是如此

代码语言:javascript
运行
复制
current_position = (position[0] + 1, position[1] + 1)
low_x = position[1]
high_x = position[1] + 3
low_y = position[0]
high_y = position[0] + 3

由于位置被索引为几次,所以将其解压缩是有意义的。我还将删除low_x和low_y,因为它们已经有(合理的)名称;x和y。

代码语言:javascript
运行
复制
x, y = position
current_position = x + 1, y + 1
low_x = x
high_x = x + 3
low_y = y
high_y = y + 3
neighbourhood = regions[low_y:high_y, low_x:high_x].copy()

那么保持变量low_x、low_y、high_x或high_y就没有意义了,它们不增加任何清晰度,也不会在其他地方使用。

代码语言:javascript
运行
复制
x, y = position
current_position = x + 1, y + 1
neighbourhood = regions[y:y+3, x:x+3].copy()

这段代码现在有神奇的常量x+3和y+3,我不知道它们是从哪里来的。

代码语言:javascript
运行
复制
adjacent_regions = sorted(...)
adjacent_regions.pop(0)

if len(adjacent_regions) == 0:
    ...
    ...
else:
    new_region_label = adjacent_regions.pop(0)
    regions[tuple(current_position)] = new_region_label
    for label in adjacent_regions:
        regions[regions == label] = new_region_label

我删除了任何与adjacent_regions无关的内容。由此我注意到了两件事。

列表结构从前面弹出一两次。通常,当从前面弹出时,列表具有O(n)复杂性,因为它需要对列表中的所有内容进行更改。尽管它可能不是一个很长的清单,但它仍然是一种我们应该尽量避免的复杂气味。

一个快速的解决办法是对列表进行反向排序,从最后而不是从一开始就弹出。在这种情况下,由于我没有看到adjacent_region暴露在函数之外,所以我们可以避免修改列表。没有从前面弹出,并且考虑到额外的元素,代码看起来可能如下所示:

代码语言:javascript
运行
复制
adjacent_regions = sorted(...)
# adjacent_regions.pop(0)  # REMOVED

if len(adjacent_regions) == 1:  # Empty other than the '0' label
    ...
    ...
else:
    # Ignoring the first element, this becomes .pop(1)
    # Then changed .pop to a simple __getitem__
    new_region_label = adjacent_regions[1]
    regions[tuple(current_position)] = new_region_label
    for label in adjacent_regions:
        regions[regions == label] = new_region_label
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/244771

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档