本文作者 @PolestarX 转载请注明出处
禁止百家号使用本文任何内容
程序基于Dentosal开源的python-sc2 和 deepmind开源pysc2(这部分教学不一定能出,训练效果不及预期)
https://github.com/Dentosal/python-sc2
https://github.com/deepmind/pysc2
相关文档:
wiki:http://wiki.sc2ai.net/Main_Page
文档目录:https://github.com/Dentosal/python-sc2/wiki
sc2.BotAI类属性(包含一些全局操作API):https://github.com/Dentosal/python-sc2/wiki/The-BotAI-class
单位及动作:https://github.com/Dentosal/python-sc2/wiki/Units-and-actions
单位能力及对应ID:https://github.com/Dentosal/python-sc2/wiki/Unit-abilities-and-IDs
原教程来自youtube sentdex 和github的 Dentosal ,修复了一些原教程在当前版本可能出现的BUG
https://www.youtube.com/watch?v=v3LJ6VvpfgI&list=PLQVvvaa0QuDcT3tPehHdisGMc8TInNqdq&index=2&t=0s
https://pythonprogramming.net/scouting-visual-input-starcraft-ii-ai-python-sc2-tutorial/
How To: PySC2 https://github.com/skjb/pysc2-tutorial
战术策略参考:
https://github.com/Dentosal/python-sc2/tree/master/examples
单位名称/科技树查阅:
https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)
https://liquipedia.net/starcraft2/Terran_Units_(Legacy_of_the_Void)
https://liquipedia.net/starcraft2/Zerg_Units_(Legacy_of_the_Void)
AI天梯:
暴雪官方开源:
s2protocol:https://github.com/Blizzard/s2protocol
s2client-proto:https://github.com/Blizzard/s2client-proto
s2client-api:https://github.com/Blizzard/s2client-api
本教程为简略的星际2 AI编写入门demo,属于娱乐性质。玩家观看此教程之后开发出的谐星AI(比如XieStar),与作者无关。希望教程能起到抛砖引玉的效果。
使用者有一点点Python基础和对星际2的粗略了解即可完成本教程。
本人不是深度学习从业人员,也没有强大的硬件支持,因此暂时不涉及深度学习相关内容。如果教程播放量比较多的话,再考虑后续的部分吧。当然英语过关的老哥也可以自行观看后续内容。deeplearning部分涉及到一些额外知识,推荐先选择性观看sentdex的machine learning series教程,这样在后期训练模型时会对一些术语有一个初步的了解。
感谢deepmind为开源社区做出的贡献,也向youtube的sentdex致敬。
以下内容可略过不看,直接跳到教程部分。
从围棋到DOTA2,AI似乎无往不利。Alphastar对战人类10:1的战绩被广泛传播,甚至有无良营销号打出AI 10:0完胜人类的标题。然而在Mana唯一获胜的那场直播中,正面操作无懈可击的Alphastar被一个棱镜耍得晕头转向,变成了F2 AI。星际2作为RTS游戏的代表,其拥有的战争迷雾机制和巨大的策略空间,对AI来说是从未有过的挑战。几百年的训练时间培养出了正面、多线操作和运营方面的怪物,然而与训练时间不相匹配的是孱弱的大局观。演示局结束后,deepmind的研发人员接受现场采访,他们并没有沉浸在巨大比分优势的喜悦中,而是很清楚地认识到,人类玩家在这11盘中都没有使用早期战术,而且最后一盘的反击则让AI的劣势显露无疑。Alphastar还有很多需要完善的地方。
最近Alphastar开始在欧服天梯上匿名训练,让星际玩家又多了几分期待。~~AI小儿只敢隐姓埋名,定是畏惧我谐星战术,真懦夫也。~~感谢暴雪创造了这个伟大的RTS游戏,感谢deepmind为我们揭示了这款游戏的别样魅力,感谢星际2的玩家们成就了电子竞技!
星际2是否是人类在游戏方面与AI抗衡的最后一个堡垒?我们拭目以待。
I don't see why a game needs to be big for someone to love playing it. ——Naniwa
第一节教程旨在了解星际2 API和python-sc2、pysc2的基本安装、使用方法,并编写一个可以自动采矿的AI。
我的开发环境:
软件:Win10、Python3.6、星际2 4.9.3版本
硬件:E3 1231v3、20GB DDR3、GTX 970M
# 必须先装好python开发环境,过程此处不再赘述
-
Mac或者Win平台安装星际2游戏,linux平台暂无(我用的win平台。不用linux的原因是linux没有完整的游戏界面)
-
https://github.com/deepmind/pysc2 clone仓库或
直接pip安装(我用的pip):
执行
pip install sc2
执行
pip install pysc2
版本号2.0.2 -
https://github.com/Blizzard/s2client-proto#downloads 下载地图包,我下载了19年第一赛季的地图
-
解压地图包到游戏目录的Maps文件夹下,如果没有就新建(密码: iagreetotheeula)注意保留地图包文件夹,不要直接把rep文件放到Maps文件夹中。
-
修改sc2包中的paths.py 修改对应平台的游戏路径(此处修改win平台)
BASEDIR = { "Windows": "F:\Games\StarCraft II", # 修改此处 "Darwin": "/Applications/StarCraft II", "Linux": "~/StarCraftII", "WineLinux": "~/.wine/drive_c/Program Files (x86)/StarCraft II", }
-
编写代码并运行,可以看到游戏界面(可能会遇到错误,解决方法看本节最后)
API查阅:https://github.com/Dentosal/python-sc2/wiki
""" 一个简单的入门程序 星际2 4.9.3版本测试通过 PvP 对手简单电脑 我方AI只采矿 地图为AutomatonLE """ import sc2 # run_game用于启动游戏并指定各项启动参数 # maps指定游戏在哪张地图上运行。Race选择种族,Difficulty选择电脑难度。 from sc2 import run_game, maps, Race, Difficulty # Bot和Computer分别指你自己写的AI和游戏内置的电脑 from sc2.player import Bot, Computer class SentdeBot(sc2.BotAI): """ 这个类就是你要写的AI类,必须要继承sc2.BotAI,很多内置方法都在其中 """ async def on_step(self, iteration: int): """ on_step这个异步方法必须被重写,再此将会调用你设置的每一步指令。 """ # distribute_workers是内置方法,代表自动让农民采矿 await self.distribute_workers() def main(): run_game(maps.get("AutomatonLE"), [ Bot(Race.Protoss, SentdeBot()), Computer(Race.Protoss, Difficulty.Easy)], realtime=True) if __name__ == '__main__': main()
那么,除了和电脑交战以外,如何评估自己创造的AI实力呢?很简单,改动一些代码即可:
这样就可以与自己创造的AI对战了,运行程序之后会出现两个窗口,一个窗口是你操作的种族(下面代码中你将操作人族),另一个窗口是你的AI(下面代码中AI使用虫族)。但必须指出的是,这样相当于双开游戏,对电脑配置要求较高(我的电脑已经非常卡了),调至职业选手画质会有很大的改善。
from sc2 import run_game, maps, Race from sc2.player import Bot, Human run_game(maps.get("AutomatonLE"), [ Human(Race.Terran), Bot(Race.Zerg, SentdeBot()) ], realtime=True)
如果游戏放在固态硬盘里,启动会快很多。效果图如下:
ValueError: 3794 is not a valid AbilityId
版本问题引起。星际2现版本为4.9.3(19年7月)
https://www.bountysource.com/issues/74655708-valueerror-3794-is-not-a-valid-abilityid
ocnuybear commented on this issue 10 days ago.
- Browse to c:\program files (x86)\microsoft visual studio\shared\python37_64\lib\site-packages\sc2 (I'm using Python in Visual Studio 2019 environment)
- Edit game_data.py and comment out 'assert self.id != 0' with # in front, save changes.
- Edit pixel_map.py and comment out 'assert self.bits_per_pixel % 8 == 0, "Unsupported pixel density"' with # in front, save changes.
Run again and game should work now fully updated on 4 Jul 2019
本节为AI增加了有余钱时自动建造水晶和探机的机制。不足之处是AI会一直建造探机,哪怕超过采矿效率上限。有概率一次补两个水晶,造成资源浪费。
部分代码如下:(完全版代码见文件夹内 ch2_Workers_and_Pylons.py )
"""
造农民和水晶
"""
import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import NEXUS, PROBE, PYLON
class SentdeBot(sc2.BotAI):
async def on_step(self, iteration: int):
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
async def build_workers(self):
"""
选择空闲基地建造农民
noqueue意味着当前建造列表为空
"""
for nexus in self.units(NEXUS).ready.noqueue:
if self.can_afford(PROBE):
await self.do(nexus.train(PROBE))
async def build_pylons(self):
"""
人口空余不足5时造水晶。
"""
if self.supply_left < 5 and not self.already_pending(PYLON):
nexuses = self.units(NEXUS).ready
if nexuses.exists:
if self.can_afford(PYLON):
await self.build(PYLON, near=nexuses.first) # near表示建造地点。后期可以用深度学习优化
"""
此处略去部分内容
"""
效果如下:
"from sc2.constants import PROBE" not working
某些IDE造成的问题,直接用IDLE可以运行。或者像我的代码里一样加上 if __name__ == '__main__':
部分代码即可。
You can avoid it by doing:
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.ability_id import AbilityId
from sc2.ids.buff_id import BuffId
from sc2.ids.upgrade_id import UpgradeId
from sc2.ids.effect_id import EffectId
This is probably the better choice because VScode and pycharm both mark the enums red if you dont have a UnitTypeId.
in front of the name, unless you disabled that warning.
Sidenote: There isn't anything we can do since these are the names directly from the SC2 API.
这一节为AI增加了采气和关键的扩张逻辑。但是,AI会直接扩张(雅典娜的惊叹),没有任何战斗单位防御,并且还是会一直补农民。run_game函数的realtime参数设为False可以加速游戏进程,方便获取结果。
部分代码
from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR
class SentdeBot(sc2.BotAI):
async def on_step(self, iteration: int):
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
"""
此处略去部分内容
"""
async def build_assimilators(self):
"""
建造气矿
"""
for nexus in self.units(NEXUS).ready:
vespenes = self.state.vespene_geyser.closer_than(25.0, nexus)
for vespene in vespenes:
if not self.can_afford(ASSIMILATOR):
break
worker = self.select_build_worker(vespene.position)
if worker is None:
break
if not self.units(ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(ASSIMILATOR, vespene))
async def expand(self):
"""
何时扩张 简化版
基地数量少于3个就立即扩张
"""
if self.units(NEXUS).amount < 3 and self.can_afford(NEXUS):
await self.expand_now()
def main():
run_game(maps.get("AutomatonLE"), [
Bot(Race.Protoss, SentdeBot()),
Computer(Race.Protoss, Difficulty.Easy)], realtime=False) # realtime设为False可以加速
if __name__ == '__main__':
main()
效果如下,AI已经学会采气(虽然有时气矿建造位置诡异),并开始开三矿:
这一节中为AI增加建造战斗单位及其前置建筑的能力。缺点是AI只会单兵营出追猎。追猎没有主动进攻能力,不会保护己方单位。不过既然AI已经有能力生产战斗单位,那么离胜利也就更进一步了。
部分代码如下:
from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR, GATEWAY, \
CYBERNETICSCORE, STALKER
class SentdeBot(sc2.BotAI):
async def on_step(self, iteration: int):
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.offensive_force_buildings()
await self.build_offensive_force()
"""
此处略去部分内容
"""
async def offensive_force_buildings(self):
"""
建造产兵建筑
"""
if self.units(PYLON).ready.exists:
pylon = self.units(PYLON).ready.random
if self.units(GATEWAY).ready.exists:
if not self.units(CYBERNETICSCORE):
if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
await self.build(CYBERNETICSCORE, near=pylon)
else:
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)
async def build_offensive_force(self):
"""
建造战斗单位
"""
for gw in self.units(GATEWAY).ready.noqueue:
if self.can_afford(STALKER) and self.supply_left > 0:
await self.do(gw.train(STALKER))
这一节将为AI的战斗单位增加防御和进攻指令,并建造多个产兵建筑。在扩张的同时,AI会建造三个BG爆追猎,当追猎数量超过5个时会主动攻击视野中的敌人,超过15个时就会主动进攻敌方出生点。
部分代码:
import random
class SentdeBot(sc2.BotAI):
async def on_step(self, iteration: int):
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.offensive_force_buildings()
await self.build_offensive_force()
await self.attack()
async def offensive_force_buildings(self):
"""
建造产兵建筑
"""
if self.units(PYLON).ready.exists:
pylon = self.units(PYLON).ready.random
if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
await self.build(CYBERNETICSCORE, near=pylon)
elif len(self.units(GATEWAY)) <= 3:
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)
def find_target(self, state):
"""
寻找敌方单位
注意这个函数不是异步的,不用加async
"""
if len(self.known_enemy_units) > 0:
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_structures) > 0:
return random.choice(self.known_enemy_structures)
else:
return self.enemy_start_locations[0]
async def attack(self):
"""
控制追猎攻击视野内敌方单位
"""
if self.units(STALKER).amount > 15: # 追猎数量够多时主动出击
for s in self.units(STALKER).idle:
await self.do(s.attack(self.find_target(self.state)))
if self.units(STALKER).amount > 5:
if len(self.known_enemy_units) > 0:
for s in self.units(STALKER).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
def main():
run_game(maps.get("AutomatonLE"), [
Bot(Race.Protoss, SentdeBot()),
Computer(Race.Protoss, Difficulty.Medium)], realtime=False) # realtime设为False可以加速
if __name__ == '__main__':
main()
我们首次战胜了简单电脑以及中等难度电脑。仅用时8分29秒,AI稳稳超越<国服第三>太子的水平:
但AI输给了拥有多种兵种组合的困难电脑,第一波15个追猎过去,电脑的不朽/虚空已经出来了。仅凭追猎要打赢还是有难度。而且战斗过程中,AI不会操作残血追猎,极大地增加了战损。
上一章AI完成了历史性任务:击败简单电脑。但是AI还不懂兵种组合,没有时间概念。这一章我们将优化产兵建筑数量,建立BG数量和时间的关系。AI在造好15个追猎就F2A了,而后续的追猎则是一个一个进攻对方基地,如果第一波进攻失败,那就成了添油战术送兵了。
注意on_setp函数的iteration参数,这将和游戏时钟建立关系。
这一节继续优化AI的建造流程。BG数量将粗略地和时间挂钩,同时生产农民也将持续优化(加上限制)。AI还将建造新的单位。同时,建造战斗单位的代码也需要调整,因为AI总是先生产便宜的单位,昂贵的单位就可能因为资源不足一直无法生产。
class SentdeBot(sc2.BotAI):
def __init__(self):
self.ITERATIONS_PER_MINUTE = 165
self.MAX_WORKERS = 80 # 限制最大农民数
async def on_step(self, iteration: int): # iteration类似游戏时钟 每分钟165个迭代(待确认)
self.iteration = iteration
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.offensive_force_buildings()
await self.build_offensive_force()
await self.attack()
async def build_workers(self):
"""
选择空闲基地建造农民
noqueue意味着当前建造列表为空
"""
if len(self.units(NEXUS)) * 24 > len(self.units(PROBE)): # 每矿农民补满就不补了
if len(self.units(PROBE)) < self.MAX_WORKERS:
for nexus in self.units(NEXUS).ready.noqueue:
if self.can_afford(PROBE):
await self.do(nexus.train(PROBE))
async def offensive_force_buildings(self):
"""
建造产兵/科技建筑
"""
print('iterations:', self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(PYLON).ready.exists:
pylon = self.units(PYLON).ready.random
# 建造BY
if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
await self.build(CYBERNETICSCORE, near=pylon)
# 建造更多BG
elif len(self.units(GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2): # 粗略计算
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)
# 这个VS放的早啊
if self.units(CYBERNETICSCORE).ready.exists:
if len(self.units(STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
await self.build(STARGATE, near=pylon)
async def build_offensive_force(self):
"""
建造战斗单位
"""
for gw in self.units(GATEWAY).ready.noqueue:
if not self.units(STALKER).amount > self.units(VOIDRAY).amount: # 粗略判断
if self.can_afford(STALKER) and self.supply_left > 0:
await self.do(gw.train(STALKER))
for sg in self.units(STARGATE).ready.noqueue:
if self.can_afford(VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(VOIDRAY))
当AI开始建造更多的建筑时,糟糕的摆放位置影响可能会越来越大。例如下图中,水晶和VS完全挡住了采矿的路径。令人智熄的操作.jpg
经过一番血战,无敌虚空为AI拿下了第一场对战困难电脑的胜利(并不是100%的胜率)180人口虚空带追猎怎么输
你也可以学习别人写好的程序,例如修电脑炮台并发表情跳他(也有可能电脑转进二矿,然后你的探机就会在别人家主矿修满地堡但就是不去别人二矿)
https://github.com/Dentosal/python-sc2/blob/master/examples/protoss/cannon_rush.py
由于星际2有非常多的变量,建筑摆放位置、建造流程、timing点、交战操作和兵种配比都是极其复杂的问题,完全硬编码想要达到很好的效果难度非常大。
到上一章为止,我们所有的策略都是硬编码的,还算不上真正的AI。从这章开始,我们要动手实现深度学习。那么问题来了:没有现成模型可用的情况下,如何应用深度学习?
首先我们要把问题简化、抽象化。或许一个可行的解决办法是:先将行为分为几个决策(攻击敌方建筑、攻击敌方单位或是防守己方基地等),再应用到空闲单位上。后续我们再考虑将学习应用到运营流程中。
我们现在知道了神经网络的输出,那么输入应该是什么呢?
有人或许会说要用经典强化学习算法 Q-learning ,但是对这种问题来说过于复杂了。这会使用极大的数据样本,意义很小并且过程繁琐。原作者更倾向于使用evolutionary learning method。
一个可用的、简单的神经网络是卷积神经网络(Convolutional Neural Networks, CNN),因为你可以用它来可视化一切。刚开始应用CNN时,你需要尽可能地简化问题。当初步实践成功时,再尝试更复杂的解决方案。
首先,我们不在把追猎放在首要位置,我们想优先生产更多虚空。但追猎也必不可少,因为要靠它抵挡前期的一波。另外,我们保持attack函数不变。我们不再需要更多的BG,造一个BG只是为了解锁后面的建筑。(但实际我们仍需要生产少量的追猎。不过为了简化问题,先只生产虚空,后续再考虑这个)。build_offensive_force函数只生产虚空,这样就将作战单位简化为了一种(单一兵种作战?)。
接下来我们就要使用测试数据并分辨正确和错误的选择。
导入两个库:cv2(OpenCV)和numpy
部分代码:
import cv2
import numpy as np
class SentdeBot(sc2.BotAI):
def __init__(self):
self.ITERATIONS_PER_MINUTE = 165
self.MAX_WORKERS = 80 # 限制最大农民数
async def on_step(self, iteration: int): # iteration类似游戏时钟 每分钟165个迭代(待确认)
self.iteration = iteration
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.offensive_force_buildings()
await self.build_offensive_force()
await self.attack()
await self.intel()
async def intel(self):
"""
原作者随便起的名字,你也可以起名为AMD_YES
该函数将游戏运行过程可视化
"""
print('dir:', dir(self)) # 你总是可以使用dir命令来获取帮助,也可以直接看源码
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # 反转图片像素
# 画出每个基地的位置
for nexus in self.units(NEXUS):
nex_pos = nexus.position
cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])),
10, (0, 255, 0), -1) # 10代表尺寸,元组代表BGR颜色格式(注意不是RGB的排列顺序),-1代表描边线宽
# 转换坐标
flipped = cv2.flip(game_data, 0) # 翻转
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow('Intel', resized)
cv2.waitKey(1) # 1ms
async def offensive_force_buildings(self):
"""
建造产兵/科技建筑
"""
if self.units(PYLON).ready.exists:
pylon = self.units(PYLON).ready.random
# 建造BY
if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
await self.build(CYBERNETICSCORE, near=pylon)
# 建造1个BG解锁科技即可
elif len(self.units(GATEWAY)) < 1:
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)
# 这个VS放的早啊
if self.units(CYBERNETICSCORE).ready.exists:
if len(self.units(STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
await self.build(STARGATE, near=pylon)
async def build_offensive_force(self):
"""
建造战斗单位(只要虚空)
"""
for sg in self.units(STARGATE).ready.noqueue:
if self.can_afford(VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(VOIDRAY))
OpenCV库将每个基地的位置显示在模拟地图上:
OpenCV生成的图形是为了让我们更好地了解到游戏内发生的事情。
在这一章,我们要描绘更多的单位,并为它们增加不同的颜色和尺寸。
async def intel(self):
"""
原作者随便起的名字,你也可以起名为amd
该函数将游戏运行过程可视化
"""
print('dir:', dir(self)) # 你总是可以使用dir命令来获取帮助,也可以直接看源码
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # 反转图片像素
# UNIT:[SIZE,(BGR COLOR)]
draw_dict = {
NEXUS: [15, (0, 255, 0)],
PYLON: [3, (20, 235, 0)],
PROBE: [1, (55, 200, 0)],
ASSIMILATOR: [2, (55, 200, 0)],
GATEWAY: [3, (200, 100, 0)],
CYBERNETICSCORE: [3, (150, 150, 0)],
STARGATE: [5, (255, 0, 0)],
VOIDRAY: [3, (255, 100, 0)],
}
# 画出每个单位的位置
for unit_type in draw_dict:
for unit in self.units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])),
draw_dict[unit_type][0], draw_dict[unit_type][1], -1)
# 转换坐标
flipped = cv2.flip(game_data, 0) # 翻转
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow('Intel', resized)
cv2.waitKey(1) # 1ms
在此之后,还要为AI增加侦查的能力(例如建造OB等)。侦查是十分重要的工作,因为神经网网络的输入就是依靠地图上能看到的信息。因此我们需要建造VR和OB。部分代码如下:
from sc2 import run_game, maps, Race, Difficulty, position
from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR, GATEWAY, \
CYBERNETICSCORE, STALKER, STARGATE, VOIDRAY, OBSERVER, ROBOTICSFACILITY
class SentdeBot(sc2.BotAI):
def __init__(self):
self.ITERATIONS_PER_MINUTE = 165
self.MAX_WORKERS = 80 # 限制最大农民数
async def on_step(self, iteration: int): # iteration类似游戏时钟 每分钟165个迭代(待确认)
self.iteration = iteration
await self.scout()
await self.distribute_workers()
await self.build_workers()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.offensive_force_buildings()
await self.build_offensive_force()
await self.attack()
await self.intel()
def random_location_variance(self, enemy_start_location):
"""
随机给出敌方主矿附近的侦查坐标
:param enemy_start_location: 敌方出生点
:return: 侦查坐标
"""
x = enemy_start_location[0]
y = enemy_start_location[1]
x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]
if x < 0:
x = 0
if y < 0:
y = 0
if x > self.game_info.map_size[0]:
x = self.game_info.map_size[0]
if y > self.game_info.map_size[1]:
y = self.game_info.map_size[1]
# 无法直接返回xy二维坐标,大概因为游戏是三维的原因。需要用sc2的position转换坐标 注意传入的是tuple
go_to = position.Point2(position.Pointlike((x, y)))
return go_to
async def scout(self):
"""
侦查部分
"""
if len(self.units(OBSERVER)) > 0:
scout = self.units(OBSERVER)[0]
if scout.is_idle:
enemy_location = self.enemy_start_locations[0]
move_to = self.random_location_variance(enemy_location)
print(move_to)
await self.do(scout.move(move_to))
else:
for rf in self.units(ROBOTICSFACILITY).ready.noqueue:
if self.can_afford(OBSERVER) and self.supply_left > 0:
await self.do(rf.train(OBSERVER))
async def intel(self):
"""
原作者随便起的名字,你也可以起名为amd
该函数将游戏运行过程可视化
"""
# print('dir:', dir(self)) # 你总是可以使用dir命令来获取帮助,也可以直接看源码
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # 反转图片像素
# UNIT:[SIZE,(BGR COLOR)]
draw_dict = {
NEXUS: [15, (0, 255, 0)],
PYLON: [3, (20, 235, 0)],
PROBE: [1, (55, 200, 0)],
ASSIMILATOR: [2, (55, 200, 0)],
GATEWAY: [3, (200, 100, 0)],
CYBERNETICSCORE: [3, (150, 150, 0)],
STARGATE: [5, (255, 0, 0)],
VOIDRAY: [3, (255, 100, 0)],
}
# 画出每个单位的位置
for unit_type in draw_dict:
for unit in self.units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])),
draw_dict[unit_type][0], draw_dict[unit_type][1], -1)
# 画出敌方单位位置
main_base_names = ["nexus", "commandcenter", "hatchery"]
for enemy_building in self.known_enemy_structures:
pos = enemy_building.position
if enemy_building.name.lower() not in main_base_names:
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
for enemy_building in self.known_enemy_structures:
pos = enemy_building.position
if enemy_building.name.lower() in main_base_names:
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
# 区分战斗单位和工作单位
for enemy_unit in self.known_enemy_units:
if not enemy_unit.is_structure:
worker_names = ["probe",
"scv",
"drone"]
# if that unit is a PROBE, SCV, or DRONE... it's a worker
pos = enemy_unit.position
if enemy_unit.name.lower() in worker_names:
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
else:
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)
# 画出OB位置,尺寸尽可能小,以突出侦查的重要信息
for obs in self.units(OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1)
# 转换坐标
flipped = cv2.flip(game_data, 0) # 翻转
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow('Intel', resized)
cv2.waitKey(1) # 1ms
async def offensive_force_buildings(self):
"""
建造产兵/科技建筑
"""
if self.units(PYLON).ready.exists:
pylon = self.units(PYLON).ready.random
# 建造BY
if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
await self.build(CYBERNETICSCORE, near=pylon)
# 建造1个BG解锁科技即可
elif len(self.units(GATEWAY)) < 1:
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)
# 造VR,准备出OB
if self.units(CYBERNETICSCORE).ready.exists:
if len(self.units(ROBOTICSFACILITY)) < 1:
if self.can_afford(ROBOTICSFACILITY) and not self.already_pending(ROBOTICSFACILITY):
await self.build(ROBOTICSFACILITY, near=pylon)
# 这个VS放的早啊
if self.units(CYBERNETICSCORE).ready.exists:
if len(self.units(STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
await self.build(STARGATE, near=pylon)
下图的白点就是OB,正飞向对方主矿侦查~~(然后就被炮台打下来了)~~
在开展任何机器学习前,我们都要建立自己的数据集。这一章我们终于可以开始做这个最后的准备工作了。
还有一点数据可视化的工作是:在图像左下角用横条形式追踪当前的资源和人口数据,为平衡战斗单位的资源/人口消耗做准备。
接下来我们要修改attack函数,同时还要引入游戏结果(胜/负),以判断训练数据的正确性。
我们对这个AI进行的第一项深度学习改造就是让AI判断攻击选择的正确与否。
async def attack(self):
"""
随机做出不同的攻击选择
"""
if len(self.units(VOIDRAY).idle) > 0:
choice = random.randrange(0, 4)
target = False
if self.iteration > self.do_something_after:
if choice == 0:
# no attack
wait = random.randrange(20, 165)
self.do_something_after = self.iteration + wait
elif choice == 1:
# attack_unit_closest_nexus
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(NEXUS)))
elif choice == 2:
# attack enemy structures
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
elif choice == 3:
# attack_enemy_start
target = self.enemy_start_locations[0]
if target:
for vr in self.units(VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4) # 表示神经网络的输出,类似 [1,0,0,0],这个list表示不攻击(choice=1)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped]) # 收集测试数据
新建一个train_data文件夹,程序在每次胜利时都会保存进攻的决策数据。每场游戏大概会做出30-60种选择。构建一个高效的数据集显然需要非常长的时间(几百局游戏)。
数据集以numpy专用格式存储,直接打开是乱码:
需要用numpy.load()函数打开
上一章中我们终于得到了自己的训练数据。原作者也提供了他自己的数据集:
https://drive.google.com/file/d/1cO0BmbUhE2HsUC5ttQrLQC_wLTdCn2-u/view
你需要在命令行中(例如powershell)安装新软件包:
pip install keras (原文中2.1.2版本无法安装)
pip install tensorflow-gpu (原文中1.8版本无法安装)
如果你不知道什么是神经网络,那么推荐你先看完以下教程中的deeplearning部分
https://pythonprogramming.net/neural-networks-machine-learning-tutorial/
训练数据解压完有13G,是的你没有看错。
add later...
本小节可以实现你自己的悍马2000。
我们先回顾一下人类选手的最顶级点毒爆操作:
在天梯局中,没有比赛时心理负担的情况下,ByuN究竟有多高的操作上限
https://www.bilibili.com/video/av12256762/?p=3 11分30秒开始
原程序需要在美服用地图编辑器下载“Marine Split Challenge-LOTV”的一张散枪兵练习图。我没法登陆美服的编辑器,就在国服找了一张练习图,效果一样。地图名为Marine Control v1.4(作者:Morrow),我把它放在resource文件夹中。值得一提的是,国服还有一张散枪兵演示图,使用银河编辑器的触发器实现甩枪兵,有兴趣的可以看一看。
第一个版本的枪兵操作(文件名:marine1.py) 可以实现完美诸葛连弩 (枪兵王?不过是个操作过硬的LOL选手)
第二个版本的枪兵操作(文件名:marine2.py) ,机枪兵在散的同时优先点毒爆和残血单位。(超级瞄准已部署!诸葛连弩算什么?)
下图就是第二个版本的演示。可以看到在不限制APM的情况下,AI可以达到五位数的瞬间APM,单独操作每个枪兵。虽然我们最希望看到的是AI从学习中获得的大局观而不是莽夫操作,但这个例子可以证明pysc2强大而丰富的功能。
https://github.com/Cryptyc/Sc2LadderServer
Sc2LadderServer是一个为星际2API所设计的一个天梯服务器。它会加载你写的星际2AI(bots)并执行对战。
此处以Linux服务器举例
-
CMake:https://cmake.org/download/
下载所需版本 我下载了cmake-3.15.0-Linux-x86_64.tar.gz
-
SC2 Linux Packages: https://github.com/Blizzard/s2client-proto#linux-packages
我下载了4.7.1
解压密码:iagreetotheeula
-
地图包:https://github.com/Blizzard/s2client-proto#map-packs
我用的 Ladder 2019 Season 1
解压密码:iagreetotheeula
-
CMake:https://blog.csdn.net/fxnawm/article/details/78489586 注意32位和64 位一定要区分开
如果是下载的含有source标签的则是源代码文件,需要自己编译,如果下载的是Binary distributions对应的则是已经编译好的版本,只需要添加环境变量就行。
执行 cmake --version ,出现版本号则说明安装成功。
-
SC2 Linux Packages:解压出StarCraft II文件夹
-
地图包:解压,解压出的文件夹放入StarCraft II/Maps中
# Get the project.
$ git clone --recursive https://github.com/Cryptyc/Sc2LadderServer.git
$ cd Sc2LadderServer
# Create build directory.
$ mkdir build && cd build
# Generate a Makefile.
# Use 'cmake -DCMAKE_BUILD_TYPE=Debug ../' if debuginfo is needed
$ cmake ../
# Build.
$ make
如果在cmake ../的过程中出现以下错误:
CMake Error: CMAKE_C_COMPILER not set, after EnableLanguage CMake Error: CMAKE_CXX_COMPILER not set, after EnableLanguage
需要更新make:apt install make
更新之后编译就可以了。下图为正常编译过程。