【星际争霸2中的强化学习-1】使用 PySC2 构建虫族机器人

中文网站上关于星际争霸2中AI模型的训练资料非常少,这里找到一篇比较好的pysc2使用的文章,翻译一下,方便阅读。

代码:GitHub - skjb/pysc2-tutorial: Tutorials for building a PySC2 botTutorials for building a PySC2 bot. Contribute to skjb/pysc2-tutorial development by creating an account on GitHub.https://hub.fastgit.org/skjb/pysc2-tutorial

在本教程中,您将构建一个基本的 Zerg 机器人,该机器人将能够生产 Zergling 并将它们发送到地图上攻击敌人。我假设您具有基本的 Python 编程知识并且能够安装 PySC2 2.0(现在已经是3.0了)。

让我们开始吧。

1. 创建基本代理

  首先,让我们导入基本模块:

python">from pysc2.agents import base_agent
from pysc2.env import sc2_env
from pysc2.lib import actions, features
from absl import app

现在我们可以创建我们的代理:

python">class ZergAgent(base_agent.BaseAgent):
  def step(self, obs):
    super(ZergAgent, self).step(obs)
    
    return actions.FUNCTIONS.no_op()

step方法是我们代理的核心部分,它是我们所有的决策发生。在每一步结束时,你必须返回一个动作,在这种情况下,动作是什么都不做。我们将很快添加更多操作。

如果您遵循我之前的教程,您会注意到操作的格式已更改,以前最后一行是:

python">return actions.FunctionCall(actions.FUNCTIONS.no_op.id, [])

我认为新格式更简单,更容易遵循,蒂莫在这里做得很好。

2. 添加运行代码

现在我们设置环境,以便我们可以运行它。在我的原始教程中,我们在命令行上运行代理并传递了许多参数,但在这里我们将添加一些代码以使其能够在没有这些额外参数的情况下运行代理。

python">def main(unused_argv):
  agent = ZergAgent()
  try:
    while True:
      with sc2_env.SC2Env(
          map_name="AbyssalReef",

我在这里选择使用深海礁地图,它比我之前教程中的简单 64 地图更有趣。

python">          players=[sc2_env.Agent(sc2_env.Race.zerg),

这里我们指定第一个玩家是我们的代理,代理的种族是虫族。您只需使用sc2_env.Race.protosssc2_env.Race.terran或者即使sc2_env.Race.random您很勇敢,也可以选择另一个种族。

python">                   sc2_env.Bot(sc2_env.Race.random,
                               sc2_env.Difficulty.very_easy)],

接下来我们指定第二个玩家是机器人,这意味着它使用了游戏的内部 AI,机器人的种族是随机的,并且难度级别非常简单。可能的难度水平very_easyeasymediummedium_hardhardhardervery_hardcheat_visioncheat_moneycheat_insane。请注意,medium_hard在游戏中实际上hard是“困难”、“更难”、harder“非常困难”和very_hard“精英”。

在此空间中,您可以选择指定另一个代理,允许两个代理相互对战!

python">          agent_interface_format=features.AgentInterfaceFormat(
              feature_dimensions=features.Dimensions(screen=84, minimap=64)),

在这里我们指定屏幕和小地图分辨率,这些是 PySC2 1.x 中的默认值。这些分辨率本质上决定了每个要素图层中有多少“像素”数据,这些图层包括地形高度、可见性和单位所有权等内容。您的机器人可以使用这些功能来做出决策。

PySC2 2.0 中的新功能之一是添加了 RGB 层,本质上是人类会看到的渲染游戏,如果您想使用该层,则需要在此处将尺寸指定为rgb_dimensions,但我们不会介绍在本教程中。

python">          step_mul=16,

此参数确定在您的机器人选择要执行的操作之前经过多少“游戏步骤”。默认设置为 8,在“正常”游戏速度下大约为 300 APM,我们将其设置为 160 以将 APM 降低到 150。在此阶段不需要额外的 APM,游戏将更快完成如果你少采取行动。

python">          game_steps_per_episode=0,

这里我们设置了每场比赛的固定长度,PySC2 1.x 中的默认值在正常速度下约为 30 分钟。在 PySC2 2.0 中,您可以将此值设置为 0 以允许游戏根据需要运行。

python">          visualize=True) as env:

最后一个参数是可选的,但是您可以方便地查看可视化,因为它包含有关您的机器人可用的所有观察层的详细信息。通过查看屏幕上呈现的这些层,您可以更好地理解它们。

python">        agent.setup(env.observation_spec(), env.action_spec())
        
        timesteps = env.reset()
        agent.reset()
        
        while True:
          step_actions = [agent.step(timesteps[0])]
          if timesteps[0].last():
            break
          timesteps = env.step(step_actions)
      
  except KeyboardInterrupt:
    pass
  
if __name__ == "__main__":
  app.run(main)

其余的代码只是关于循环、将步骤详细信息提供给代理、接收动作并重复直到游戏结束或直到游戏终止。

现在您可以运行您的代理:

python zerg_agent.py

您应该看到您的特工收集矿物,直到它最终被敌人占领。

3. 选择无人机

在 Zerg 代理可以生产任何 Zergling 之前,它需要一个产卵池。为了建立一个产卵池,我们需要选择一个无人机,所以让我们这样做。 PySC2 2.0 的一项很酷的新功能是添加了功能单元。我知道这个功能很酷,因为我做到了。它允许您通过屏幕要素图层获取有关屏幕上您无法观察或可能更困难的单位的信息。我们将使用特征单元来选择无人机。 另一个很酷的新功能之一是添加了一个单位列表,它允许您使用单位名称检索单位类型。以前我们用类似的东西编码我们自己的单位类型,_TERRAN_SCV = 45但现在我们可以使用类似的东西units.Terran.SCV。我也做了这个功能:) 让我们将单元列表添加到模块导入中:

python">from pysc2.lib import actions, features, units
import random

我们还需要这个random模块来做一些事情。

接下来我们需要启用特征单元:

python">        agent_interface_format=features.AgentInterfaceFormat(
            feature_dimensions=features.Dimensions(screen=84, minimap=64),
            use_feature_units=True),

现在,在该step()方法中,让我们使用特征单元功能来获取屏幕上所有无人机的列表:

python">  def step(self, obs):
    super(ZergAgent, self).step(obs)
  
    drones = [unit for unit in obs.observation.feature_units
              if unit.unit_type == units.Zerg.Drone]

这很酷,对吧?!多亏了新的点符号,一切看起来都更干净了,谢谢 Timo!以前你可能做过类似的事情:

python">unit_type = obs.observation["screen"][features.SCREEN_FEATURES.unit_type.index]
unit_y, unit_x = (unit_type == 104).nonzero()

现在,让我们选择一个无人机:

python">    if len(drones) > 0:
      drone = random.choice(drones)
      
      return actions.FUNCTIONS.select_point("select_all_type", (drone.x,
                                                                drone.y))

select_all_type此处的参数就像 CTRL+单击,因此将选择屏幕上的所有无人机。如您所见,无人机的xy坐标可作为属性访问。还有更多的属性,你可以访问,如healthshieldsenergybuild_progress,重要的ideal_harvestersassigned_harvesters对基地和vespene。

如果您愿意,您现在可以运行代理,以确保您的无人机被选中。

4. 建立一个产卵池

在前面的代码之上,让我们确保我们选择了一个 Drone。让我们为我们的类添加一个小的实用方法:
python">  def unit_type_is_selected(self, obs, unit_type):
    if (len(obs.observation.single_select) > 0 and
        obs.observation.single_select[0].unit_type == unit_type):
      return True
    
    if (len(obs.observation.multi_select) > 0 and
        obs.observation.multi_select[0].unit_type == unit_type):
      return True
    
    return False

此代码检查单选和多选以查看第一个选择的单位是否是正确的类型。在顶部,step()我们可以将此方法用于:

python">  def step(self, obs):
    super(ZergAgent, self).step(obs)
    
    if self.unit_type_is_selected(obs, units.Zerg.Drone):

接下来我们要确保我们可以构建一个产卵池。如果我们没有足够的矿物质,这可能是不可能的,如果我们不先检查,就会导致崩溃:

python">      if (actions.FUNCTIONS.Build_SpawningPool_screen.id in 
          obs.observation.available_actions):

然后我们可以在屏幕上随机选择一个点,希望在一些蠕变:

python">        x = random.randint(0, 83)
        y = random.randint(0, 83)
        
        return actions.FUNCTIONS.Build_SpawningPool_screen("now", (x, y))

最后,我们将随机坐标输入到动作中。

试一试,看看效果如何。运气好的话,您将拥有大量的产卵池。我们如何阻止我们所有的无人机成为产卵池?

让我们添加另一种实用方法来选择具有给定单位类型的单位:

python">  def get_units_by_type(self, obs, unit_type):
    return [unit for unit in obs.observation.feature_units
            if unit.unit_type == unit_type]

这与您上面的代码相同,但unit_type可以根据需要换出。让我们用方法来替换之前的代码:

python">    drones = self.get_units_by_type(obs, units.Zerg.Drone)

然后我们可以使用该方法来获取产卵池:

python">    spawning_pools = self.get_units_by_type(obs, units.Zerg.SpawningPool)
    if len(spawning_pools) == 0:
      if self.unit_type_is_selected(obs, units.Zerg.Drone):
        if (actions.FUNCTIONS.Build_SpawningPool_screen.id in 
            obs.observation.available_actions):
          x = random.randint(0, 83)
          y = random.randint(0, 83)
          
          return actions.FUNCTIONS.Build_SpawningPool_screen("now", (x, y))
        
      drones = self.get_units_by_type(obs, units.Zerg.Drone)
      if len(drones) > 0:
        drone = random.choice(drones)

        return actions.FUNCTIONS.select_point("select_all_type", (drone.x,
                                                                  drone.y))

那就更好了,如果我们还没有一个产卵池,我们现在只会建造一个产卵池。

5. 建立跳虫

差不多好了!现在我们有了一个产卵池,我们终于可以建造一些跳虫了。让我们从选择屏幕上的所有幼虫开始:

python">    larvae = self.get_units_by_type(obs, units.Zerg.Larva)
    if len(larvae) > 0:
      larva = random.choice(larvae)
      
      return actions.FUNCTIONS.select_point("select_all_type", (larva.x,
                                                                larva.y))

然后我们可以创建一些跳虫。将此代码放在上一个块的正上方:

python">    if self.unit_type_is_selected(obs, units.Zerg.Larva):
      if (actions.FUNCTIONS.Train_Zergling_quick.id in 
          obs.observation.available_actions):
        return actions.FUNCTIONS.Train_Zergling_quick("now")

给它一个测试,你应该得到一对跳虫。

这很好,但我们无法建造足够多的跳虫来发挥作用,我们的供应有限。我们需要霸主。

6. 产生更多的霸主

当我们选择了幼虫,如果我们没有免费供应,我们可以产生一个霸王:

python">      free_supply = (obs.observation.player.food_cap -
                     obs.observation.player.food_used)
      if free_supply == 0:
        if (actions.FUNCTIONS.Train_Overlord_quick.id in
            obs.observation.available_actions):
          return actions.FUNCTIONS.Train_Overlord_quick("now")

最后一件事我不喜欢,让我们添加另一种方法:

python">  def can_do(self, obs, action):
    return action in obs.observation.available_actions

我们现在可以替换动作检查:

python">        if self.can_do(obs, actions.FUNCTIONS.Build_SpawningPool_screen.id):

和:

python">      if self.can_do(obs, actions.FUNCTIONS.Train_Zergling_quick.id):

和:

python">        if self.can_do(obs, actions.FUNCTIONS.Train_Overlord_quick.id):

如果你现在测试你的机器人,你会发现它会产生很多跳虫,是时候攻击了!

7. 攻击

在我们进攻之前,我们需要知道我们在哪里,敌人在哪里。出于本教程的目的,我们将假设生成位置始终位于左上角和右下角。所以我们需要找出我们在哪里,然后攻击对方。 让我们创建一个__init__()方法来初始化一个变量:

python">  def __init__(self):
    super(ZergAgent, self).__init__()
    
    self.attack_coordinates = None

step()方法的开头,添加以下内容:

python">  def step(self, obs):
    super(ZergAgent, self).step(obs)
    
    if obs.first():
      player_y, player_x = (obs.observation.feature_minimap.player_relative ==
                            features.PlayerRelative.SELF).nonzero()
      xmean = player_x.mean()
      ymean = player_y.mean()
      
      if xmean <= 31 and ymean <= 31:
        self.attack_coordinates = (49, 49)
      else:
        self.attack_coordinates = (12, 16)

obs.first()代码检查它是否是游戏的第一步。然后我们在小地图上获得我们单位的中心xy坐标。有一个功能层可以根据它们所属的人在小地图上显示所有单位。我将在以后的教程中更详细地解释这一点。

现在我们有了我们的攻击位置,让我们开始攻击。首先我们选择我们的军队:

python">    zerglings = self.get_units_by_type(obs, units.Zerg.Zergling)
    if len(zerglings) > 0:
      if self.can_do(obs, actions.FUNCTIONS.select_army.id):
        return actions.FUNCTIONS.select_army("select")

在此之前,我们可以攻击:

python">    if len(zerglings) > 0:
      if self.unit_type_is_selected(obs, units.Zerg.Zergling):
        if self.can_do(obs, actions.FUNCTIONS.Attack_minimap.id):
            return actions.FUNCTIONS.Attack_minimap("now",
                                                    self.attack_coordinates)

测试一下。该死,我们的小狗被咬了!让我们等到我们有更多的跳虫后再进攻:

python">    if len(zerglings) >= 10:

效果好多了~


http://www.niftyadmin.cn/n/957359.html

相关文章

防止async出错的方法

async function helloAsync() {try{await new Promise(function (resolve, reject) {throw new Error(错误);}); } catch(e) { } return await(hello); } helloAsync().then(v > console.log(v)) //catch(e > console.log(e.message));以上是js文件&#xff0c;以下是htm…

pygame将当前画面输出到变量

from PIL import Image import pygame import numpy as nppygame.display.update() # 更新画面 pil_string_image pygame.image.tostring(screen, "RGB", False) # 将当前图像输出为字符变量 pli_image Image.frombytes(RGB, screen.get_size(), pil_string_image…

错误的这个在html的console里也是有的,刚才是看花眼了吗

<!DOCTYPE html> <html><head><meta charset"UTF-8"></head><body><script>async function helloAsync() {await new Promise(function (resolve, reject) {throw new Error(错误);}); } helloAsync().then(v > consol…

强化学习调参实践

1、如果出现神经网络输出数值很大&#xff0c;而且过快收敛问题&#xff0c;如下 那么有可能是state没有除255。 重新试试 2、ValueError: The parameter probs has invalid values或模型输出nan 模型输入的数据可能为&#xff08;1&#xff0c;N&#xff0c;84&#xff0c;8…

promise的用法,为什么不是运行出3呢?ex是什么意思?

const promise new Promise(function(resolve, reject) {setTimeout(function() {try{let c 6 / 2 ;resolve(c);} catch(ex) {reject(ex);}}, 1000) })不知道怎么debug得出3来。 为啥是underfined&#xff1f; <!DOCTYPE html> <html><head><meta cha…

ray安装踩坑记录

Ray是个复杂的库&#xff0c;安装非常依赖各个包的版本&#xff0c;只要按照时间顺序&#xff0c;逐个安装即可。 坑一&#xff1a; 当前ray的最新版本为1.8.0&#xff0c;对应的cloudpickle为2.0.0&#xff0c;无法执行 ray.init() 报错为typeerror&#xff1a;cant pickl…

【Pytorch异常笔记】Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized.

异常描述 OMP: Error #15: Initializing libiomp5md.dll, but found libiomp5md.dll already initialized. OMP: Hint This means that multiple copies of the OpenMP runtime have been linked into the program. That is dangerous, since it can degrade performance or c…

zip函数和zip(*)函数

zip() 函数用于将可迭代的对象作为参数&#xff0c;将对象中对应的元素打包成一个个元组&#xff0c;然后返回由这些元组组成的列表。 如果各个迭代器的元素个数不一致&#xff0c;则返回列表长度与最短的对象相同。 zip(*)函数利用 * 号操作符&#xff0c;可以将元组解压为列…