战网下载sc2
python3(这里用python3.7.4)
下载游戏地图,前往Blizzard s2client的“ 地图包”部分并下载一些地图。
https://github.com/Blizzard/s2client-proto#map-packs
下载好后将地图放入SC2的安装目录的Maps下(没有Maps则新建)结构为:
为了方便以星灵种族为例:
游戏内容参考资料 https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)
安装sc2包 pip3 install sc2
如果sc2是自定义安装:
打开sc2内的paths.py(位于C:\Users***\AppData\Local\Programs\Python\Python37\Lib\site-packages\sc2\paths.py)
BASEDIR = {
“Windows”: “C:/Program Files (x86)/StarCraft II”, #更改匹配路径
“Darwin”: “/Applications/StarCraft II”,
“Linux”: “~/StarCraftII”,
“WineLinux”: “~/.wine/drive_c/Program Files (x86)/StarCraft II”, }
创建一个py文件如test.py
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
也可以只写第一行,这样写只是为了方便
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#每一步做什么
await self.distribute_workers() #父类中的方法
开局有12名工人,首先给他们平均分配工作,将工作人员分配到所有基地。sc2.BotAI父类有一个名为的方法distribute_workers正好可以实现这个功能(如果觉得不够精细,后面也可以自己写分发功能)
想要了解如何实现的可以看这
async def distribute_workers(self, resource_ratio: float = 2):
"""
Distributes workers across all the bases taken.
Keyword `resource_ratio` takes a float. If the current minerals to gas
ratio is bigger than `resource_ratio`, this function prefer filling geysers
first, if it is lower, it will prefer sending workers to minerals first.
This is only for workers that need to be moved anyways, it will NOT will
geysers on its own.
NOTE: This function is far from optimal, if you really want to have
refined worker control, you should write your own distribution function.
For example long distance mining control and moving workers if a base was killed
are not being handled.
WARNING: This is quite slow when there are lots of workers or multiple bases.
"""
if not self.state.mineral_field or not self.workers or not self.townhalls.ready:
return
actions = []
worker_pool = [worker for worker in self.workers.idle]
bases = self.townhalls.ready
geysers = self.geysers.ready
# list of places that need more workers
deficit_mining_places = []
for mining_place in bases | geysers:
difference = mining_place.surplus_harvesters
# perfect amount of workers, skip mining place
if not difference:
continue
if mining_place.is_vespene_geyser:
# get all workers that target the gas extraction site
# or are on their way back from it
local_workers = self.workers.filter(
lambda unit: unit.order_target == mining_place.tag
or (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag)
)
else:
# get tags of minerals around expansion
local_minerals_tags = {
mineral.tag for mineral in self.state.mineral_field if mineral.distance_to(mining_place) <= 8
}
# get all target tags a worker can have
# tags of the minerals he could mine at that base
# get workers that work at that gather site
local_workers = self.workers.filter(
lambda unit: unit.order_target in local_minerals_tags
or (unit.is_carrying_minerals and unit.order_target == mining_place.tag)
)
# too many workers
if difference > 0:
for worker in local_workers[:difference]:
worker_pool.append(worker)
# too few workers
# add mining place to deficit bases for every missing worker
else:
deficit_mining_places += [mining_place for _ in range(-difference)]
# prepare all minerals near a base if we have too many workers
# and need to send them to the closest patch
if len(worker_pool) > len(deficit_mining_places):
all_minerals_near_base = [
mineral
for mineral in self.state.mineral_field
if any(mineral.distance_to(base) <= 8 for base in self.townhalls.ready)
]
# distribute every worker in the pool
for worker in worker_pool:
# as long as have workers and mining places
if deficit_mining_places:
# choose only mineral fields first if current mineral to gas ratio is less than target ratio
if self.vespene and self.minerals / self.vespene < resource_ratio:
possible_mining_places = [place for place in deficit_mining_places if not place.vespene_contents]
# else prefer gas
else:
possible_mining_places = [place for place in deficit_mining_places if place.vespene_contents]
# if preferred type is not available any more, get all other places
if not possible_mining_places:
possible_mining_places = deficit_mining_places
# find closest mining place
current_place = min(deficit_mining_places, key=lambda place: place.distance_to(worker))
# remove it from the list
deficit_mining_places.remove(current_place)
# if current place is a gas extraction site, go there
if current_place.vespene_contents:
actions.append(worker.gather(current_place))
# if current place is a gas extraction site,
# go to the mineral field that is near and has the most minerals left
else:
local_minerals = [
mineral for mineral in self.state.mineral_field if mineral.distance_to(current_place) <= 8
]
target_mineral = max(local_minerals, key=lambda mineral: mineral.mineral_contents)
actions.append(worker.gather(target_mineral))
# more workers to distribute than free mining spots
# send to closest if worker is doing nothing
elif worker.is_idle and all_minerals_near_base:
target_mineral = min(all_minerals_near_base, key=lambda mineral: mineral.distance_to(worker))
actions.append(worker.gather(target_mineral))
else:
# there are no deficit mining places and worker is not idle
# so dont move him
pass
await self.do_actions(actions)
上面的方法在sc2/sc2.BotAI文件中
调用run_game方法设定合适的参数,这里AI选定星灵,敌方选择人类势力的简单人机
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)
运行文件可以看到如下:
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#每一步做什么
await self.distribute_workers() #父类中的方法
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)
之前的代码只能分配工人,现在需要来写一个创造工人的方法
在星灵种族(Protoss)中主基地叫(Nexus)工人叫(Probe)
详见游戏内容参考资料 https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)
为了制造工人,根据关系图可知,要先访问Nexus,还要为了与工人交互,访问Probe,为了增加人口上限,访问Pylon。
from sc2.constants import UnitTypeId
async def build_workers(self):
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
async def build_workers(self):
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)
自定义方法expand()
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
当需要进行进一步的发展时,需要从主基地附件的Vespene收集瓦斯,这种建筑物称为assimilator。
范围可做调整,15左右差不多。
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(25, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
async def build_workers(self):
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(25, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)
为了简单起见,建议构建单一的兵种,这里选择能够攻击地面和空中单位的兵种。
这里选择stalker为例,因为现在更容易使用。
可以看到它的制造前提为:
控制核心(Cybernet Core)是关于研究的,只需要造一个,如果需要加速出兵,则可以多造(Gateway)。
由于选择的是Protoss,使用需要在水晶塔的范围内才能建造建筑物
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay
if self.units(UnitTypeId.GATEWAY).ready.exists:
#如果没有CyberneticsCore,而且能支付的起且没有正在建造中的,则建造CyberneticsCore
if not self.units(UnitTypeId.CYBERNETICSCORE):
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and \
not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果不存在,且能够支付得起,而且没有正在建造中的,则建造
else:
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
详情见注释
类似于建造工人
async def build_army(self):
#遍历所有已经建好的且没有队列的Gateway
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
#如果能支付得起且可用人口大于0,则训练Stalker
if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
await self.do(gw.train(UnitTypeId.STALKER))
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
#建造能生产军队的建筑
await self.build_army_buildings()
#建造军队
await self.build_army()
async def build_workers(self):
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(25, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay
if self.units(UnitTypeId.GATEWAY).ready.exists:
#如果没有CyberneticsCore,而且能支付的起且没有正在建造中的,则建造CyberneticsCore
if not self.units(UnitTypeId.CYBERNETICSCORE):
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and \
not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果不存在,且能够支付得起,而且没有正在建造中的,则建造
else:
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
async def build_army(self):
#遍历所有已经建好的且没有队列的Gateway
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
#如果能支付得起且可用人口大于0,则训练Stalker
if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
await self.do(gw.train(UnitTypeId.STALKER))
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = False)
有了造兵的功能,现在可以指挥军队了
async def attack_enemy(self):
if self.units(UnitTypeId.STALKER).amount > 3:
if len(self.known_enemy_units) > 0:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
如果小兵的数量大于3且发现了敌人,则遍历在闲置状态的小兵去攻击一个随机的敌人。
首先需要一个用来寻找敌方目标的方法
自定义方法find_target()
def find_target(self):
#如果发现了敌方单位
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_enemy(self):
#总兵数大于3,保守
if self.units(UnitTypeId.STALKER).amount > 3:
if len(self.known_enemy_units) > 0:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
#总兵数大于15,激进
if self.units(UnitTypeId.STALKER).amount > 15:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(self.find_target()))
目前的问题是AI没有时间概念,而且没有合理的平衡工人和军队的人数
经过测试每分钟大约165次迭代
#初始化
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
在on_step中添加记录
async def on_step(self, iteration):
#记录已经迭代的次数
self.iteration = iteration
......
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于分钟数
elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
如果要动态的控制工人的数量,则:
改进后建造工人的方法
async def build_workers(self):
#动态控制工人数量
if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
与其一次扩张3个基地,不如按照时间来扩张,这样更加合理
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE ) and \
self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
由于后期Stalker不强,不如建立一支空军(Void Ray)
要想建立(Void Ray)还需要一个建筑(Stargate)
再次改进(军队建筑)
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于分钟数/2
elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
造兵
async def build_army(self):
#遍历所有已经建好的且没有队列的Gateway
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
if self.units(UnitTypeId.STALKER).amount <= self.units(UnitTypeId.VOIDRAY).amount:
#如果能支付得起且可用人口大于0,则训练Stalker
if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
await self.do(gw.train(UnitTypeId.STALKER))
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
async def attack_enemy(self):
#[0:进攻, 1:防守]
army_types = {UnitTypeId.STALKER : [15, 5], UnitTypeId.VOIDRAY : [8, 3]}
for n in army_types:
# 激进
if self.units(n).amount > army_types[n][0]:
for s in self.units(n).idle:
await self.do(s.attack(self.find_target()))
# 保守
if self.units(n).amount > army_types[n][1]:
if len(self.known_enemy_units) > 0:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
import sc2
from sc2 import run_game, maps, Race, Difficulty #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#初始化
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#记录已经迭代的次数
self.iteration = iteration
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
#建造能生产军队的建筑
await self.build_army_buildings()
#建造军队
await self.build_army()
#军队作战
await self.attack_enemy()
async def build_workers(self):
#动态控制工人数量
if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE / 2) and \
self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于分钟数/2
elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
async def build_army(self):
#遍历所有已经建好的且没有队列的Gateway
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
if self.units(UnitTypeId.STALKER).amount <= self.units(UnitTypeId.VOIDRAY).amount:
#如果能支付得起且可用人口大于0,则训练Stalker
if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
await self.do(gw.train(UnitTypeId.STALKER))
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
async def attack_enemy(self):
#[0:进攻, 1:防守]
army_types = {UnitTypeId.STALKER : [15, 5], UnitTypeId.VOIDRAY : [8, 3]}
for n in army_types:
# 激进
if self.units(n).amount > army_types[n][0]:
for s in self.units(n).idle:
await self.do(s.attack(self.find_target()))
# 保守
if self.units(n).amount > army_types[n][1]:
if len(self.known_enemy_units) > 0:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
def find_target(self):
#如果发现了敌方单位
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]
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Hard)], realtime = False)
现在基本上能够战胜困难人机了
现阶段可以从GitHub上下载别人的AI来进行对战。
尝试添加深度学习的进化算法。
使用遗传进化算法可以使获胜的成为训练数据(基因库),失败的则被遗忘(淘汰)。
只使用voidray
建筑
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于1
elif len(self.units(UnitTypeId.GATEWAY)) < 1:
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
造兵
async def build_army(self):
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
攻击
async def attack_enemy(self):
#[0:进攻, 1:防守]
army_types = {#UnitTypeId.STALKER : [15, 5],
UnitTypeId.VOIDRAY : [8, 3]}
for n in army_types:
for s in self.units(n).idle:
await self.do(s.attack(self.find_target()))
可以选择全部进攻或者不进攻。
在整个游戏中有许多的变量,我们要可视化此数据,并且传给神经网络,建议使用卷积神经网络。
安装cv2,numpy,matplotlib
import cv2
import numpy as np
自定义一个方法,用来可视化数据
"""攻击之前执行此操作"""
async def intel(self):
#200 176 3 按行绘制RGB(200个矩阵)
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
for nexus in self.units(UnitTypeId.NEXUS):
n_pos = nexus.position
cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), 10, (0,255,0), -1)
#图像垂直翻转
flipped = cv2.flip(game_data, 0)
#调整图像大小,原图像,输出图像所需大小,比例因子
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow("Intel", resized)
cv2.waitKey(1)
首先,我们要可视化其余单元。创建一个draw_dict
#绘制己方所有单元
draw_dict = {
UnitTypeId.NEXUS: [15, (0, 255, 0)],
UnitTypeId.PYLON: [3, (20, 235, 0)],
UnitTypeId.PROBE: [1, (55, 200, 0)],
UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
UnitTypeId.GATEWAY: [3, (200, 100, 0)],
UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
UnitTypeId.STARGATE: [5, (255, 0, 0)],
UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
}
for unit in draw_dict:
for n in self.units(unit).ready:
n_pos = n.position
cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][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"]
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)
组建一个侦查员(observer)还需要一个建筑(Robotics Facility)
更改方法
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于1
elif len(self.units(UnitTypeId.GATEWAY)) < 1:
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
# 如果有了控制核心,则建造侦察兵基地
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于1
if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
添加自定义方法(侦查员)
def random_location_variance(self, enemy_start_location):
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]
go_to = position.Point2(position.Pointlike((x, y)))
return go_to
#侦察兵
async def scout(self):
#有则行动
if len(self.units(UnitTypeId.OBSERVER)) > 0:
scout = self.units(UnitTypeId.OBSERVER)[0]
#闲置的侦察兵
if scout.is_idle:
enemy_location = self.enemy_start_locations[0]
#侦察敌人出生地附近的位置
move_to = self.random_location_variance(enemy_location)
await self.do(scout.move(move_to))
#没有则建造
else:
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
import sc2
from sc2 import run_game, maps, Race, Difficulty,position #导入了运行游戏、地图、种族、难度、位置
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
import cv2
import numpy as np
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#初始化
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#记录已经迭代的次数
self.iteration = iteration
#侦察兵
await self.scout()
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
#建造能生产军队的建筑
await self.build_army_buildings()
#建造军队
await self.build_army()
#可视化
await self.intel()
#军队作战
await self.attack_enemy()
async def build_workers(self):
#动态控制工人数量
if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and \
self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于1
elif len(self.units(UnitTypeId.GATEWAY)) < 1:
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
# 如果有了控制核心,则建造侦察兵基地
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于1
if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
#侦察兵
async def scout(self):
#有则行动
if len(self.units(UnitTypeId.OBSERVER)) > 0:
scout = self.units(UnitTypeId.OBSERVER)[0]
#闲置的侦察兵
if scout.is_idle:
enemy_location = self.enemy_start_locations[0]
#侦察敌人出生地附近的位置
move_to = self.random_location_variance(enemy_location)
await self.do(scout.move(move_to))
#没有则建造
else:
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
async def build_army(self):
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
async def attack_enemy(self):
#[0:进攻, 1:防守]
army_types = {UnitTypeId.VOIDRAY: [8, 3]}
for UNIT in army_types:
if self.units(UNIT).amount > army_types[UNIT][0]:
for s in self.units(UNIT).idle:
await self.do(s.attack(self.find_target()))
elif self.units(UNIT).amount > army_types[UNIT][1]:
if len(self.known_enemy_units) > 0:
for s in self.units(UNIT).idle:
await self.do(s.attack(random.choice(self.known_enemy_units)))
def find_target(self):
#如果发现了敌方单位
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]
def random_location_variance(self, enemy_start_location):
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]
go_to = position.Point2(position.Pointlike((x, y)))
return go_to
"""攻击之前执行此操作"""
async def intel(self):
#200 176 3 按行绘制RGB(200个矩阵)
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
#绘制己方所有单元
draw_dict = {
UnitTypeId.NEXUS: [15, (0, 255, 0)],
UnitTypeId.PYLON: [3, (20, 235, 0)],
UnitTypeId.PROBE: [1, (55, 200, 0)],
UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
UnitTypeId.GATEWAY: [3, (200, 100, 0)],
UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
UnitTypeId.STARGATE: [5, (255, 0, 0)],
UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
UnitTypeId.OBSERVER: [1, (255, 255, 255)],
UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
}
for unit in draw_dict:
for n in self.units(unit).ready:
n_pos = n.position
cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][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"]
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)
#图像垂直翻转
flipped = cv2.flip(game_data, 0)
#调整图像大小,原图像,输出图像所需大小,比例因子
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow("Intel", resized)
cv2.waitKey(1)
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Hard)], realtime = True)
绘制数据线
#其他数据的可视化
line_max = 50
#矿物数量/1500
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0
#瓦斯气的数量/1500
vespene_ratio = self.vespene / 1500
if vespene_ratio > 1.0:
vespene_ratio = 1.0
#可用人口比例
population_ratio = self.supply_left / self.supply_cap
if population_ratio > 1.0:
population_ratio = 1.0
#人口上限数/200
plausible_supply = self.supply_cap / 200.0
#voidray占全部人口的比例
military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0
#绘制数据线line(图片, 起点, 终点, RGB, 粗细)
# voidray占全部人口的比例
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口上限数/200
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# 可用人口比例
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 瓦斯气的数量/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 矿物数量/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)
首先添加两个变量
class SentBot(sc2.BotAI):
#初始化
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
#记录上一次迭代数
self.do_something_after = 0
#训练数据
self.train_data = []
将攻击方法改为随机决策
async def attack_enemy(self):
#如果voidray有闲置的
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
#随机一种决策
choice = random.randrange(0, 4)
#定义目标
target = False
#如果迭代数大于上一次的迭代数
if self.iteration > self.do_something_after:
#不攻击,等待20-165个迭代
if choice == 0:
wait = random.randrange(20, 165)
self.do_something_after = self.iteration + wait
#攻击离家最近的敌方单位
elif choice == 1:
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
#攻击敌方建筑物
elif choice == 2:
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
#攻击敌方出生地
elif choice == 3:
target = self.enemy_start_locations[0]
#如果目标不为空,则让所有闲置的voidray做该决策
if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
#每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped])
设置是否要可视化,添加
#类外添加
HEADLESS = False
...
#图像垂直翻转
self.flipped = cv2.flip(game_data, 0)
#是否可视化
if not HEADLESS:
# 调整图像大小,原图像,输出图像所需大小,比例因子
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
cv2.imshow("Intel", resized)
cv2.waitKey(1)
只需要记录胜利的数据,因此添加on_end方法
#结束
def on_end(self, game_result: Result):
print("!!!!!!game over!!!!!!")
print(game_result)
if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result #导入了运行游戏、地图、种族、难度、位置、结果
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
import cv2
import numpy as np
import time
HEADLESS = False
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#初始化
def __init__(self):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
#记录上一次迭代数
self.do_something_after = 0
#训练数据
self.train_data = []
#结束
def on_end(self, game_result: Result):
if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
print("!!!!!!game over!!!!!!")
print(game_result)
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#记录已经迭代的次数
self.iteration = iteration
#侦察兵
await self.scout()
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
#建造能生产军队的建筑
await self.build_army_buildings()
#建造军队
await self.build_army()
#可视化
await self.intel()
#军队作战
await self.attack_enemy()
async def build_workers(self):
#动态控制工人数量
if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and \
self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于1
elif len(self.units(UnitTypeId.GATEWAY)) < 1:
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
# 如果有了控制核心,则建造侦察兵基地
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于1
if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
#侦察兵
async def scout(self):
#有则行动
if len(self.units(UnitTypeId.OBSERVER)) > 0:
scout = self.units(UnitTypeId.OBSERVER)[0]
#闲置的侦察兵
if scout.is_idle:
enemy_location = self.enemy_start_locations[0]
#侦察敌人出生地附近的位置
move_to = self.random_location_variance(enemy_location)
await self.do(scout.move(move_to))
#没有则建造
else:
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
async def build_army(self):
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
async def attack_enemy(self):
#如果voidray有闲置的
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
#随机一种决策
choice = random.randrange(0, 4)
#定义目标
target = False
#如果迭代数大于上一次的迭代数
if self.iteration > self.do_something_after:
#不攻击,等待20-165个迭代
if choice == 0:
wait = random.randrange(20, 165)
self.do_something_after = self.iteration + wait
#攻击离家最近的敌方单位
elif choice == 1:
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
#攻击敌方建筑物
elif choice == 2:
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
#攻击敌方出生地
elif choice == 3:
target = self.enemy_start_locations[0]
#如果目标不为空,则让所有闲置的voidray做该决策
if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
#每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped])
def find_target(self):
#如果发现了敌方单位
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]
#目标附近的随机位置
def random_location_variance(self, enemy_start_location):
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]
go_to = position.Point2(position.Pointlike((x, y)))
return go_to
"""攻击之前执行此操作"""
async def intel(self):
#200 176 3 按行绘制RGB(200个矩阵)
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
#绘制己方所有单元
draw_dict = {
UnitTypeId.NEXUS: [15, (0, 255, 0)],
UnitTypeId.PYLON: [3, (20, 235, 0)],
UnitTypeId.PROBE: [1, (55, 200, 0)],
UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
UnitTypeId.GATEWAY: [3, (200, 100, 0)],
UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
UnitTypeId.STARGATE: [5, (255, 0, 0)],
UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
UnitTypeId.OBSERVER: [1, (255, 255, 255)],
UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
}
for unit in draw_dict:
for n in self.units(unit).ready:
n_pos = n.position
cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][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"]
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)
#其他数据的可视化
line_max = 50
#矿物数量/1500
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0
#瓦斯气的数量/1500
vespene_ratio = self.vespene / 1500
if vespene_ratio > 1.0:
vespene_ratio = 1.0
#可用人口比例
population_ratio = self.supply_left / self.supply_cap
if population_ratio > 1.0:
population_ratio = 1.0
#人口上限数/200
plausible_supply = self.supply_cap / 200.0
#voidray占全部人口的比例
military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0
#绘制数据线line(图片, 起点, 终点, RGB, 粗细)
# voidray占全部人口的比例
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口上限数/200
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# 可用人口比例
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 瓦斯气的数量/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 矿物数量/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)
#图像垂直翻转
self.flipped = cv2.flip(game_data, 0)
#是否可视化
if not HEADLESS:
# 调整图像大小,原图像,输出图像所需大小,比例因子
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
cv2.imshow("Intel", resized)
cv2.waitKey(1)
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"),
[Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)],
realtime=False)
安装tensorflow-gpu、keras
import keras
from keras.models import Sequential #导入顺序模型
from keras.layers import Dense, Dropout, Flatten #导入全连接层、输入丢弃、输入展平
from keras.layers import Conv2D, MaxPooling2D #导入2D卷积层、2D空间最大池化
from keras.callbacks import TensorBoard #基本可视化
import numpy as np
import os
import random
#建立模型
model = Sequential()
#隐藏卷积层
model.add(Conv2D(32, (3, 3), padding="same", input_shape=(176, 200, 3), activation="relu"))
model.add(Conv2D(32, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(64, (3, 3), padding="same", activation="relu"))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(128, (3, 3), padding='same', activation='relu'))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
#完全连接的密集层
model.add(Flatten())
model.add(Dense(512, activation="relu"))
model.add(Dropout(0.5))
#输出层
model.add(Dense(4, activation="softmax"))
#设置神经网络编译配置
#学习率1e-4
learning_rate = 0.0001
#优化器
opt = keras.optimizers.adam(lr=learning_rate, decay=1e-6)
model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])
#通过TensorBoard记录
tensorboard = TensorBoard(log_dir="logs/stage1")
不支持gpu版本的也可以用cpu
import keras
from keras.models import Sequential #导入顺序模型
from keras.layers import Dense, Dropout, Flatten #导入全连接层、输入丢弃、输入展平
from keras.layers import Conv2D, MaxPooling2D #导入2D卷积层、2D空间最大池化
from keras.callbacks import TensorBoard #基本可视化
import numpy as np
import os
import random
"""建立模型"""
model = Sequential()
#隐藏卷积层
model.add(Conv2D(32, (3, 3), padding="same", input_shape=(176, 200, 3), activation="relu"))
model.add(Conv2D(32, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(64, (3, 3), padding="same", activation="relu"))
model.add(Conv2D(64, (3, 3), activation="relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(128, (3, 3), padding='same', activation='relu'))
model.add(Conv2D(128, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
#完全连接的密集层
model.add(Flatten())
model.add(Dense(512, activation="relu"))
model.add(Dropout(0.5))
#输出层
model.add(Dense(4, activation="softmax"))
#设置神经网络编译配置
#学习率1e-4
learning_rate = 0.0001
#优化器
opt = keras.optimizers.Adam(lr=learning_rate, decay=1e-6)
model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])
#通过TensorBoard记录
tensorboard = TensorBoard(log_dir="logs/stage1")
"""训练模型"""
#训练数据存放的文件夹
train_data_dir = "Train_data"
#世代数
epochs = 10
"""平衡数据的长度"""
def check_data():
choices = {"no_attacks" : no_attacks,
"attack_closest_to_nexus" : attack_closest_to_nexus,
"attack_enemy_structures" : attack_enemy_structures,
"attack_enemy_start" : attack_enemy_start}
total_data = 0
lengths = []
#统计每种决策的长度
for choice in choices:
print("Length of {} is: {}".format(choice, len(choices[choice])))
total_data += len(choices[choice])
lengths.append(len(choices[choice]))
print("Total data length now is:", total_data)
return lengths
for i in range(epochs):
current = 0
increment = 200 #一次200增量,每次读200个文件
not_maximum = True
#加载所有文件
all_files = os.listdir(train_data_dir)
maximum = len(all_files)
#将文件随机排序
random.shuffle(all_files)
while not_maximum:
print("Working On {} : {}".format(current, current + increment))
no_attacks = []
attack_closest_to_nexus = []
attack_enemy_structures = []
attack_enemy_start = []
for file in all_files[current: current + increment]:
#完整路径
full_path = os.path.join(train_data_dir, file)
#data = 一局游戏的数据
data = np.load(full_path, allow_pickle=True)
data = list(data)
#一次进攻
for d in data:
#得到最大数坐标,列如:
"""
[0. 1. 0. 0.]
1
[0. 0. 1. 0.]
2
"""
choice = np.argmax(d[0])
if choice == 0:
no_attacks.append([d[0], d[1]])
elif choice == 1:
attack_closest_to_nexus.append([d[0], d[1]])
elif choice == 2:
attack_enemy_structures.append([d[0], d[1]])
elif choice == 3:
attack_enemy_start.append([d[0], d[1]])
#取得四种决策数量的列表,如[29, 65, 84, 77]
lengths = check_data()
# 得到了四种决策中的最小数量
lowest_data = min(lengths)
#将列表元素重新排序
random.shuffle(no_attacks)
random.shuffle(attack_closest_to_nexus)
random.shuffle(attack_enemy_structures)
random.shuffle(attack_enemy_start)
#使数据的数据量相同
no_attacks = no_attacks[:lowest_data]
attack_closest_to_nexus = attack_closest_to_nexus[:lowest_data]
attack_enemy_structures = attack_enemy_structures[:lowest_data]
attack_enemy_start = attack_enemy_start[:lowest_data]
#重新统计
check_data()
#将所有决策加入更大的列表中
train_data = no_attacks + attack_closest_to_nexus + attack_enemy_structures + attack_enemy_start
#将其中所有元素重新排序
random.shuffle(train_data)
#print(train_data)
#保留100个样本进行验证
test_size = 100
#分批训练的样本数
batch_size = 128
#调整输入数据,x=game_data, y=决策表
#剔除验证样本
x_train = np.array([i[1] for i in train_data[:-test_size]]).reshape(-1, 176, 200, 3)
y_train = np.array([i[0] for i in train_data[:-test_size]])
#验证样本
x_test = np.array([i[1] for i in train_data[-test_size:]]).reshape(-1, 176, 200, 3)
y_test = np.array([i[0] for i in train_data[-test_size:]])
#开始拟合数据
model.fit(x_train, y_train,
batch_size=batch_size,
validation_data=(x_test, y_test),
shuffle=True,
verbose=1, callbacks=[tensorboard])
#保存模型并继续迭代:
model.save("BasicCNN-{}-epochs-{}-LR-STAGE1".format(epochs, learning_rate))
current += increment
if current > maximum:
not_maximum = False
import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result #导入了运行游戏、地图、种族、难度、位置、结果
from sc2.player import Bot, Computer #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
import cv2
import numpy as np
import time
import os
import keras
HEADLESS = False
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
#初始化
def __init__(self, use_model=False):
#每分钟迭代数
self.ITERATIONS_PER_MINUTE = 165
#定义最大工人数
self.MAX_WORKERS = 50
#记录上一次迭代数
self.do_something_after = 0
#训练数据
self.train_data = []
#是否使用模型
self.use_model = use_model
#导入模型
if self.use_model:
print("use model")
self.model = keras.models.load_model("BasicCNN-1-epochs-0.0001-LR-STAGE1")
#结束
def on_end(self, game_result: Result):
# if game_result == Result.Victory:
# np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
print("!!!!!!game over!!!!!!")
print(game_result, self.use_model)
with open("gameout-random-vs-medium.txt", "a") as f:
if self.use_model:
f.write("Model {}\n".format(game_result))
else:
f.write("Random {}\n".format(game_result))
#用async异步就不需要等待每一个完成再做其他事
async def on_step(self, iteration):
#记录已经迭代的次数
self.iteration = iteration
#侦察兵
await self.scout()
#每一步做什么
await self.distribute_workers() #父类中的方法
#建造工人
await self.build_workers()
#建造水晶塔
await self.build_pylons()
#扩张基地
await self.expand()
#建造瓦斯收集器
await self.build_assimilator()
#建造能生产军队的建筑
await self.build_army_buildings()
#建造军队
await self.build_army()
#可视化
await self.intel()
#军队作战
await self.attack_enemy()
async def build_workers(self):
#动态控制工人数量
if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
#遍历全部的主基地(已经准备好的且没有队列的)
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
#如果能够支付的起工人
if self.can_afford(UnitTypeId.PROBE):
#让该目标制造一个工人
await self.do(nexus.train(UnitTypeId.PROBE))
async def build_pylons(self):
#如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
#找到已经建好的主基地
nexuses = self.units(UnitTypeId.NEXUS).ready
#如果主基地存在
if nexuses.exists:
#如果能够支付的起水晶塔
if self.can_afford(UnitTypeId.PYLON):
#在主基地附近建造水晶塔
await self.build(UnitTypeId.PYLON, near=nexuses.first)
async def expand(self):
#如果基地总数小于n且能够支付的起基地建造的费用
if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and \
self.can_afford(UnitTypeId.NEXUS):
#父类扩张基地方法
await self.expand_now()
async def build_assimilator(self):
#遍历所有已经建好的主基地
for nexus in self.units(UnitTypeId.NEXUS).ready:
#定义所有离该主基地25个单元格的瓦斯间歇泉
vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vespene in vespenes:
#如果支付不起则退出
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
#否则就在瓦斯泉的附近选一个工人
worker = self.select_build_worker(vespene.position)
#如果没有工人则退出
if not worker:
break
#如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
async def build_army_buildings(self):
#首先需要找到水晶塔
if self.units(UnitTypeId.PYLON).ready.exists:
#如果有水晶塔,则选择一个随机的水晶塔
pylon = self.units(UnitTypeId.PYLON).ready.random
#如果已经存在GateWay且没有CyberneticsCore
if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
#如果能支付的起且没有正在建造中的,则建造CyberneticsCore
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
#如果gateway的数量小于1
elif len(self.units(UnitTypeId.GATEWAY)) < 1:
#且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#如果有了控制核心,则建立Stargate
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于分钟数/2
if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
# 且能够支付得起,而且没有正在建造中的,则建造
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)
# 如果有了控制核心,则建造侦察兵基地
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
#数量小于1
if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
#侦察兵
async def scout(self):
#有则行动
if len(self.units(UnitTypeId.OBSERVER)) > 0:
scout = self.units(UnitTypeId.OBSERVER)[0]
#闲置的侦察兵
if scout.is_idle:
enemy_location = self.enemy_start_locations[0]
#侦察敌人出生地附近的位置
move_to = self.random_location_variance(enemy_location)
await self.do(scout.move(move_to))
#没有则建造
else:
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
async def build_army(self):
#空军
for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
await self.do(sg.train(UnitTypeId.VOIDRAY))
async def attack_enemy(self):
#如果voidray有闲置的
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
#随机一种决策
# choice = random.randrange(0, 4)
#定义目标
target = False
#如果迭代数大于上一次的迭代数
if self.iteration > self.do_something_after:
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
# print('prediction: ',choice)
choice_dict = {0: "No Attack!",
1: "Attack close to our nexus!",
2: "Attack Enemy Structure!",
3: "Attack Eneemy Start!"}
print("Choice #{}:{}".format(choice, choice_dict[choice]))
else:
choice = random.randrange(0, 4)
#不攻击,等待20-165个迭代
if choice == 0:
wait = random.randrange(20, 165)
self.do_something_after = self.iteration + wait
#攻击离家最近的敌方单位
elif choice == 1:
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
#攻击敌方建筑物
elif choice == 2:
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
#攻击敌方出生地
elif choice == 3:
target = self.enemy_start_locations[0]
#如果目标不为空,则让所有闲置的voidray做该决策
if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
#每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
y = np.zeros(4)
y[choice] = 1
# print(y)
self.train_data.append([y, self.flipped])
def find_target(self):
#如果发现了敌方单位
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]
#目标附近的随机位置
def random_location_variance(self, enemy_start_location):
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]
go_to = position.Point2(position.Pointlike((x, y)))
return go_to
"""攻击之前执行此操作"""
async def intel(self):
#200 176 3 按行绘制RGB(200个矩阵)
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
#绘制己方所有单元
draw_dict = {
UnitTypeId.NEXUS: [15, (0, 255, 0)],
UnitTypeId.PYLON: [3, (20, 235, 0)],
UnitTypeId.PROBE: [1, (55, 200, 0)],
UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
UnitTypeId.GATEWAY: [3, (200, 100, 0)],
UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
UnitTypeId.STARGATE: [5, (255, 0, 0)],
UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
UnitTypeId.OBSERVER: [1, (255, 255, 255)],
UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
}
for unit in draw_dict:
for n in self.units(unit).ready:
n_pos = n.position
cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][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"]
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)
#其他数据的可视化
line_max = 50
#矿物数量/1500
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0
#瓦斯气的数量/1500
vespene_ratio = self.vespene / 1500
if vespene_ratio > 1.0:
vespene_ratio = 1.0
#可用人口比例
population_ratio = self.supply_left / self.supply_cap
if population_ratio > 1.0:
population_ratio = 1.0
#人口上限数/200
plausible_supply = self.supply_cap / 200.0
#voidray占全部人口的比例
military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0
#绘制数据线line(图片, 起点, 终点, RGB, 粗细)
# voidray占全部人口的比例
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口上限数/200
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# 可用人口比例
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 瓦斯气的数量/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 矿物数量/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)
#图像垂直翻转
self.flipped = cv2.flip(game_data, 0)
#是否可视化
if not HEADLESS:
# 调整图像大小,原图像,输出图像所需大小,比例因子
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
cv2.imshow("Intel", resized)
cv2.waitKey(1)
"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"),
[Bot(Race.Protoss, SentBot(use_model=True)), Computer(Race.Terran, Difficulty.Hard)],
realtime=False)