准备

我的环境是python3.6,sc2包0.11.1

机器学习包下载链接:pysc2

地图下载链接:maps

游戏下载链接:国际服 国服

pysc2是DeepMind开发的星际争霸Ⅱ学习环境。 它是封装星际争霸Ⅱ机器学习API,同时也提供Python增强学习环境。

以神族为例编写代码,神族建筑科技图如下:

教程

采矿

# -*- encoding: utf-8 -*-
'''
@File : __init__.py.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/11/3 12:32 Jonas None
''' import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer class SentdeBot(sc2.BotAI):
async def on_step(self, iteration: int):
await self.distribute_workers() run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
],realtime = True)

注意

game_data.py的assert self.id != 0注释掉

pixel_map.py的assert self.bits_per_pixel % 8 == 0, "Unsupported pixel density"注释掉

否则会报错

运行结果如下,农民开始采矿

可以正常采矿

建造农民和水晶塔

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import * 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):
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿
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) ### 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
],realtime = True)

运行结果如下,基地造农民,农民造水晶

收集气体和开矿

代码如下

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import * 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_workers(self):
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene)) ## 开矿
async def expand(self):
if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

run_game的realtime设置成False,可以在加速模式下运行游戏。

运行效果如下:



可以建造吸收厂和开矿

建造军队

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import * 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 build_workers(self):
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene)) ## 开矿
async def expand(self):
if self.units(UnitTypeId.NEXUS).amount<2 and self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.units(UnitTypeId.PYLON).ready.exists:
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists:
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_offensive_force(self):
# 无队列化建造
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
await self.do(gw.train(UnitTypeId.STALKER)) ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

运行结果如下:



可以看到,我们建造了折跃门和控制核心并训练了追猎者

控制部队进攻

代码如下


import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
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 build_workers(self):
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene)) ## 开矿
async def expand(self):
if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
elif len(self.units(UnitTypeId.GATEWAY))<=3:
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY,near=pylon) ## 造兵
async def build_offensive_force(self):
# 无队列化建造
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
await self.do(gw.train(UnitTypeId.STALKER)) ## 寻找目标
def find_target(self,state):
if len(self.known_enemy_units)>0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units)>0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
# 追猎者数量超过15个开始进攻
if self.units(UnitTypeId.STALKER).amount>15:
for s in self.units(UnitTypeId.STALKER).idle:
await self.do(s.attack(self.find_target(self.state))) # 防卫模式:视野范围内存在敌人,开始攻击
if self.units(UnitTypeId.STALKER).amount>5:
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))) ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

运行结果如下





可以看到,4个折跃门训练追猎者并发动进攻。

击败困难电脑

我们目前的代码只能击败中等和简单电脑,那么如何击败困难电脑呢?

代码如下


import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
import random class SentdeBot(sc2.BotAI):
def __init__(self):
# 经过计算,每分钟大约165迭代次数
self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 65 async def on_step(self, iteration: int):
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):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS))*16>len(self.units(UnitTypeId.PROBE)) and len(self.units(UnitTypeId.PROBE))<self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount<self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/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)
# 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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_offensive_force(self):
# 无队列化建造
for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount: 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)) ## 寻找目标
def find_target(self,state):
if len(self.known_enemy_units)>0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units)>0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
# {UNIT: [n to fight, n to defend]}
aggressive_units = {UnitTypeId.STALKER: [15, 5],
UnitTypeId.VOIDRAY: [8, 3]} for UNIT in aggressive_units:
# 攻击模式
if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
1]:
for s in self.units(UNIT).idle:
await self.do(s.attack(self.find_target(self.state)))
# 防卫模式
elif self.units(UNIT).amount > aggressive_units[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)))
## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

运行结果如下



可以看到,击败了困难人族电脑,但是电脑选择了rush战术,我们写得AI脚本会输掉游戏。显然,这不是最佳方案。

“只有AI才能拯救我的胜率”,请看下文。

采集地图数据

这次我们只造一个折跃门,全力通过星门造虚空光辉舰

修改offensive_force_buildings(self)方法的判断

elif len(self.units(GATEWAY)) < 1:
if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
await self.build(GATEWAY, near=pylon)

注释或者删除build_offensive_force(self)的建造追猎者的代码

        ## 造兵
async def build_offensive_force(self):
# 无队列化建造
# for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
# if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
#
# 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))

attack(self)中的aggressive_units注释掉Stalker

导入numpy和cv2库

game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

建立以地图Heigt为行,Width为列的三维矩阵

for nexus in self.units(NEXUS):
nex_pos = nexus.position
print(nex_pos)
cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1) # BGR

遍历星灵枢纽,获取下一个位置,画圆,circle(承载圆的img, 圆心, 半径, 颜色, thickness=-1表示填充)

接下来我们要垂直翻转三维矩阵,因为我们建立的矩阵左上角是原点(0,0),纵坐标向下延申,横坐标向右延申。翻转之后就成了正常的坐标系。

flipped = cv2.flip(game_data, 0)

图像缩放,达到可视化最佳。

        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow('Intel', resized)
cv2.waitKey(1)

至此,完整代码如下

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2 class SentdeBot(sc2.BotAI):
def __init__(self):
# 经过计算,每分钟大约165迭代次数
self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 65 async def on_step(self, iteration: int):
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.intel()
await self.attack() async def intel(self):
# 根据地图建立的三维矩阵
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):
nex_pos = nexus.position
# circle(承载圆的img, 圆心, 半径, 颜色, thickness=-1表示填充)
# 记录星灵枢纽的位置
cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1)
# 图像翻转垂直镜像
flipped = cv2.flip(game_data, 0)
# 图像缩放
# cv2.resize(原图像,输出图像的大小,width方向的缩放比例,height方向缩放的比例)
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
cv2.imshow('Intel', resized) # cv2.waitKey(每Xms刷新图像)
cv2.waitKey(1) # 建造农民
async def build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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_offensive_force(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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
# {UNIT: [n to fight, n to defend]}
aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]} for UNIT in aggressive_units:
# 攻击模式
if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][1]:
for s in self.units(UNIT).idle:
await self.do(s.attack(self.find_target(self.state)))
# 防卫模式
elif self.units(UNIT).amount > aggressive_units[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))) ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

运行结果如下



采集到了地图位置。

侦察

在intel(self)里创建一个字典draw_dict,UnitTypeId作为key,半径和颜色是value


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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.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", "supplydepot", "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)

在offensive_force_buildings(self)方法中添加建造机械台

            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)

创建scout(),训练Observer

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))

生成随机位置,很简单。意思是横坐标累计递增-0.2和0.2倍的横坐标,限制条件为如果x超过横坐标,那么就是横坐标最大值。

纵坐标同理。

    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

完整代码如下

# -*- encoding: utf-8 -*-
'''
@File : demo.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/11/3 12:32 Jonas None
''' import sc2
from sc2 import run_game, maps, Race, Difficulty, position
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2 class SentdeBot(sc2.BotAI):
def __init__(self):
# 经过计算,每分钟大约165迭代次数
self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 50 async def on_step(self, iteration: int):
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.intel()
await self.attack() ## 侦察
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)
print(move_to)
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 intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # UnitTypeId作为key,半径和颜色是value
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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
# OBSERVER: [3, (255, 255, 255)],
} 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", "supplydepot", "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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) # flip horizontally to make our final fix in visual representation:
flipped = cv2.flip(game_data, 0)
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2) cv2.imshow('Intel', resized)
cv2.waitKey(1) 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 build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造机械台
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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) # 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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_offensive_force(self):
# 无队列化建造
# for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
# if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
#
# 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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
# {UNIT: [n to fight, n to defend]}
aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]} for UNIT in aggressive_units:
# 攻击模式
if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
1]:
for s in self.units(UNIT).idle:
await self.do(s.attack(self.find_target(self.state)))
# 防卫模式
elif self.units(UNIT).amount > aggressive_units[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))) ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
], realtime=False)

运行结果如下,红色和粉红色是敌方单位。

创建训练数据

统计资源、人口和军队人口比,在intel方法添加如下代码

        # 追踪资源、人口和军队人口比
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0 # 农民/人口 worker/supply ratio
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口/200 plausible supply (supply/200.0)
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# (人口-现有人口)/人口 population ratio (supply_left/supply)
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 气体/1500 gas/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 晶体矿/1500 minerals minerals/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

运行结果如下,左下角自上而下依次是“农民/人口”,“人口/200”,“(人口-现有人口)/人口”,“气体/1500”,“晶体矿/1500”



采集进攻行为数据,在attack方法中加入如下代码

        if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
choice = random.randrange(0, 4)
target = False
if self.iteration > self.do_something_after:
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] if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped])

输出如下结果

···
[1. 0. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]
[0. 0. 1. 0.]
[1. 0. 0. 0.]
···

为了使用self.flipped = cv2.flip(game_data, 0),修改

        flipped = cv2.flip(game_data, 0)
resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

        self.flipped = cv2.flip(game_data, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)

init 方法添加do_something_after和train_data

    def __init__(self):
self.ITERATIONS_PER_MINUTE = 165
self.MAX_WORKERS = 50
self.do_something_after = 0
self.train_data = []

采集攻击数据的时候不需要画图,我们在类前加HEADLESS = False,intel方法代码修改如下

        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方法,只存储胜利的数据,在和代码同级目录新建train_data文件夹

    def on_end(self, game_result):
print('--- on_end called ---')
print(game_result) if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))

完整代码如下

import os
import time import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2 HEADLESS = True
# os.environ["SC2PATH"] = 'F:\StarCraft II' class SentdeBot(sc2.BotAI):
def __init__(self):
# 经过计算,每分钟大约165迭代次数
self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 50
self.do_something_after = 0
self.train_data = [] def on_end(self, game_result):
print('--- on_end called ---')
print(game_result) if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data)) async def on_step(self, iteration: int):
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.intel()
await self.attack() ## 侦察
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)
print(move_to)
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 intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # UnitTypeId作为key,半径和颜色是value
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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
# OBSERVER: [3, (255, 255, 255)],
} 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", "supplydepot", "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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) # 追踪资源、人口和军队人口比
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0 # 农民/人口 worker/supply ratio
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口/200 plausible supply (supply/200.0)
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# (人口-现有人口)/人口 population ratio (supply_left/supply)
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 气体/1500 gas/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 晶体矿/1500 minerals minerals/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3) # flip horizontally to make our final fix in visual representation:
self.flipped = cv2.flip(game_data, 0) if HEADLESS:
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) cv2.imshow('Intel', resized)
cv2.waitKey(1) 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 build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
# print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造机械台
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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) # 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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_offensive_force(self):
# 无队列化建造
# for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
# if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
#
# 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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
choice = random.randrange(0, 4)
target = False
if self.iteration > self.do_something_after:
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] if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped]) ## 启动游戏
run_game(maps.get("AcidPlantLE"), [
Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

可以看到train_data文件夹下存储了胜利数据

创建神经网络模型

sequential model如下



选择Sequential模型,dense层,dropout和flatten,将数据flatten之后再进入dense层。最终,我们使用卷积神经网络,所以我们需要Conv2D和MaxPooling2D。我们想可视化输出模型训练结果,再加上TensorBoard。

存储数据用numpy,os操作IO,random用来进行随机操作。

import keras
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D
from keras.callbacks import TensorBoard
import numpy as np
import os
import random

首先,创建模型

model = Sequential()

名词解释:

Sequential:序贯模型,序贯模型是函数式模型的简略版,为最简单的线性、从头到尾的结构顺序,不分叉

Conv2D:图像的空域卷积

MaxPooling2D:空域信号施加到最大值池化

Dropout:用于防止过拟合

Flatten:输入"压平",把多维的输入一维化,常用在卷积层到全连接层的过渡

Dense:全连接层

接下来,创建hidden卷积层。

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'))

为神经网络设置compile属性

learning_rate = 0.0001
opt = keras.optimizers.adam(lr=learning_rate, decay=1e-6) model.compile(loss='categorical_crossentropy',
optimizer=opt,
metrics=['accuracy'])

compile方法解释:

compile(self, optimizer, loss, metrics=None, sample_weight_mode=None)

optimizer: 字符串(预定义优化器名)或优化器对象,参考优化器

loss: 字符串(预定义损失函数名)或目标函数,参考损失函数

metrics: 列表,包含评估模型在训练和测试时的网络性能的指标,典型用法是metrics=[‘accuracy’]

sample_weight_mode:如果你需要按时间步为样本赋权(2D权矩阵),将该值设为“temporal”。

默认为“None”,代表按样本赋权(1D权)。在下面fit函数的解释中有相关的参考内容。

kwargs: 使用TensorFlow作为后端请忽略该参数,若使用Theano作为后端,kwargs的值将会传递给 K.function

通过TensorBoard记录数据

tensorboard = TensorBoard(log_dir="logs/stage1")

完整代码如下:

# -*- encoding: utf-8 -*-
'''
@File : building-model.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/11/26 22:37 Jonas None
''' import keras
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D
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')) 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(log_dir="logs/stage1") tran_data_dir = "train_data"

训练神经网络模型

train_data现成的训练文件链接,当然,你也可以自己训练。

链接:https://pan.baidu.com/s/1X0eRXakbmqmL_It9QbLYfQ

提取码:trow

一次处理200个文件

for i in range(hm_epochs):
print(i)
current = 0
increment = 200
not_maximum = True

获取文件数组首元素,即choice

    while not_maximum:
print("现在正在做 {}:{}".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 = np.load(full_path)
data = list(data)
for d in data:
# 返回最大值的索引
choice = np.argmax(d[0])
if choice == 0:
no_attacks.append(d)
elif choice == 1:
attack_closest_to_nexus.append(d)
elif choice == 2:
attack_enemy_structures.append(d)
elif choice == 3:
attack_enemy_start.append(d)

统计四种进攻策略的数量

# 对数据进行统计
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

将四种进攻策略的数据规范到同一长度

        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()

把四种进攻策略合并成一个大list

        train_data = no_attacks + attack_closest_to_nexus + attack_enemy_structures + attack_enemy_start

100个验证集,len(train_data-100)个训练集

        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:]])

拟合数据

fit方法参数解释:

fit(x=None, y=None, batch_size=None, epochs=1, verbose=1, callbacks=None, validation_split=0.0, validation_data=None, shuffle=True, class_weight=None, sample_weight=None, initial_epoch=0, steps_per_epoch=None, >validation_steps=None)

x: 训练数据的 Numpy 数组(如果模型只有一个输入), 或者是 Numpy 数组的列表(如果模型有多个输入)。 如果模型中的输入层被命名,你也可以传递一个字典,将输入层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例如 >TensorFlow 数据张量)数据,x 可以是 None(默认)。

y: 目标(标签)数据的 Numpy 数组(如果模型只有一个输出), 或者是 Numpy 数组的列表(如果模型有多个输出)。 如果模型中的输出层被命名,你也可以传递一个字典,将输出层名称映射到 Numpy 数组。 如果从本地框架张量馈送(例>如 TensorFlow 数据张量)数据,y 可以是 None(默认)。

batch_size: 整数或 None。每次梯度更新的样本数。如果未指定,默认为 32。

verbose: 0, 1 或 2。日志显示模式。 0 = 安静模式, 1 = 进度条, 2 = 每轮一行。

validation_data: 元组 (x_val,y_val) 或元组 (x_val,y_val,val_sample_weights), 用来评估损失,以及在每轮结束时的任何模型度量指标。 模型将不会在这个数据上进行训练。这个参数会覆盖 validation_split。

shuffle: 布尔值(是否在每轮迭代之前混洗数据)或者 字符串 (batch)。 batch 是处理 HDF5 数据限制的特殊选项,它对一个 batch 内部的数据进行混洗。 当 steps_per_epoch 非 None 时,这个参数无效。

callbacks: 一系列的 keras.callbacks.Callback 实例。一系列可以在训练时使用的回调函数。

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(hm_epochs, learning_rate))
current += increment
if current > maximum:
not_maximum = False

至此,完整代码如下

import keras
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten,Conv2D,MaxPooling2D
from keras.callbacks import TensorBoard
import numpy as np
import os
import random # 创建序贯模型
model = Sequential()
# 创建hidden卷积层
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')) 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(log_dir="logs/stage1") train_data_dir = "train_data" # 对数据进行统计
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 # 循环次数
hm_epochs = 10 for i in range(hm_epochs):
print(i)
current = 0
increment = 200
not_maximum = True
# 遍历所有文件
all_files = os.listdir(train_data_dir)
maximum = len(all_files)
random.shuffle(all_files) while not_maximum:
print("现在正在做 {}:{}".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 = np.load(full_path)
data = list(data)
for d in data:
# 返回最大值的索引
choice = np.argmax(d[0])
if choice == 0:
no_attacks.append(d)
elif choice == 1:
attack_closest_to_nexus.append(d)
elif choice == 2:
attack_enemy_structures.append(d)
elif choice == 3:
attack_enemy_start.append(d) 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_enemy_start + attack_enemy_structures + attack_closest_to_nexus
random.shuffle(train_data)
test_size = 100
batch_size = 128
# 重置为四维数组,行未知,留给numpy计算
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(hm_epochs, learning_rate))
current += increment
if current > maximum:
not_maximum = False

调用神经网络模型

我们自己决定是否使用模型,加上use_model

    def __init__(self,use_model = False):
# 经过计算,每分钟大约165迭代次数
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-30-epochs-0.0001-LR-4.2")

记录游戏结果,是否使用模型

    def on_end(self, game_result):
print('--- on_end called ---')
print(game_result, self.use_model) with open("log.txt","a") as f:
if self.use_model:
f.write("Model {}\n".format(game_result))
else:
f.write("Random {}\n".format(game_result))

既使用模型也使用随机

 async def attack(self):

        if len(self.units(VOIDRAY).idle) > 0:

            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)

运行游戏,别忘了加入use_model参数

## 启动游戏
for i in range(10):
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot(use_model=True)),
Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

测试,查看log.txt记录的游戏结果

至此,demo.py完整代码如下:

import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2
import keras HEADLESS = False
# os.environ["SC2PATH"] = 'F:\StarCraft II' class SentdeBot(sc2.BotAI):
def __init__(self,use_model = False):
# 经过计算,每分钟大约165迭代次数
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-30-epochs-0.0001-LR-4.2") def on_end(self, game_result):
print('--- on_end called ---')
print(game_result, self.use_model) with open("log.txt","a") as f:
if self.use_model:
f.write("Model {}\n".format(game_result))
else:
f.write("Random {}\n".format(game_result)) async def on_step(self, iteration: int):
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.intel()
await self.attack() ## 侦察
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)
print(move_to)
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 intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # UnitTypeId作为key,半径和颜色是value
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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
# OBSERVER: [3, (255, 255, 255)],
} 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", "supplydepot", "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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) # 追踪资源、人口和军队人口比
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0 # 农民/人口 worker/supply ratio
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口/200 plausible supply (supply/200.0)
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# (人口-现有人口)/人口 population ratio (supply_left/supply)
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 气体/1500 gas/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 晶体矿/1500 minerals minerals/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3) # flip horizontally to make our final fix in visual representation:
self.flipped = cv2.flip(game_data, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) if not HEADLESS:
if self.use_model:
cv2.imshow('Model Intel', resized)
cv2.waitKey(1)
else:
cv2.imshow('Random Intel', resized)
cv2.waitKey(1) 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 build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
# print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造机械台
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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) # 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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_offensive_force(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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0: if self.use_model:
prediction = self.model.predict([self.flipped.reshape(-1,176,200,3)])
choice = np.argmax(prediction[0]) choice_dict = {0: "No Attack!",
1: "Attack close to our nexus!",
2: "Attack Enemy Structure!",
3: "Attack Enemy Start!"}
print("Choice #{}:{}".format(choice, choice_dict[choice]))
else:
choice = random.randrange(0, 4) target = False
if self.iteration > self.do_something_after:
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] if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped]) ## 启动游戏
for i in range(10):
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot(use_model=True)),
Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

log.txt输出结果如下

···
Model Result.Defeat
Model Result.Victory
Model Result.Defeat
Model Result.Victory
Model Result.Victory
Model Result.Defeat
Model Result.Victory
Model Result.Victory
Model Result.Victory
Model Result.Victory
Model Result.Victoryd
Model Result.Defeat
Model Result.Victory
Model Result.Victory
Model Result.Defeat
Model Result.Victory
Model Result.Defeat
Model Result.Victory
Model Result.Victory
Model Result.Defeat
Model Result.Defeat
Model Result.Victory
···

打败中等难度电脑的胜率大约59%,还需要改进

小优化

获取游戏时间,game loop, 22.4 per second on faster game speed

    async def on_step(self, iteration):
#self.iteration = iteration
self.timeMinutes = (self.state.game_loop/22.4) / 60

在random_location_variance方法里,修改enemy_start_locationself.game_info.map_size

    def random_location_variance(self, enemy_start_location):
x = enemy_start_location[0]
y = enemy_start_location[1] # 修改
x += ((random.randrange(-20, 20))/100) * self.game_info.map_size[0]
y += ((random.randrange(-20, 20))/100) * self.game_info.map_size[1]

expand方法修改,将self.iteration / self.ITERATIONS_PER_MINUTE修改成self.timeMinutes/2

    async def expand(self):
try:
if self.units(NEXUS).amount < self.timeMinutes/2 and self.can_afford(NEXUS):
await self.expand_now()
except Exception as e:
print(str(e))

offensive_force_buildings方法修改,将self.iteration / self.ITERATIONS_PER_MINUTE) / 2修改成self.timeMinutes

            if self.units(CYBERNETICSCORE).ready.exists:
if len(self.units(STARGATE)) < self.timeMinutes: # 在这里修改
if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
await self.build(STARGATE, near=pylon)

attack方法修改,将self.iteration修改成self.timeMinutes,“什么都不做“的等待时间修复

    async def attack(self):

        if len(self.units(VOIDRAY).idle) > 0:

            target = False
if self.timeMinutes > self.do_something_after: # 这里修改
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
else:
choice = random.randrange(0, 4) if choice == 0:
# no attack
wait = random.randrange(7,100)/100 # 这里修改
self.do_something_after = self.timeMinutes+ wait

完整代码如下

# -*- encoding: utf-8 -*-
'''
@File : demo.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/11/3 12:32 Jonas None
'''
import os
import time import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2
import keras HEADLESS = False # os.environ["SC2PATH"] = 'F:\StarCraft II' class SentdeBot(sc2.BotAI):
def __init__(self, use_model=False):
# 经过计算,每分钟大约165迭代次数
# self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 50
self.do_something_after = 0
self.train_data = []
self.use_model = use_model
self.scouts_and_spots = {} if self.use_model:
print("use model")
self.model = keras.models.load_model("BasicCNN-30-epochs-0.0001-LR-4.2") def on_end(self, game_result):
print('--- on_end called ---')
print(game_result, self.use_model) with open("log.txt", "a") as f:
if self.use_model:
f.write("Model {}\n".format(game_result))
else:
f.write("Random {}\n".format(game_result)) async def on_step(self, iteration):
# self.iteration = iteration
self.timeMinutes = ((self.state.game_loop / 22.4) / 60) print('Time:', self.timeMinutes) 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.intel()
await self.attack() ## 侦察
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)
print(move_to)
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 intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # UnitTypeId作为key,半径和颜色是value
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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
# OBSERVER: [3, (255, 255, 255)],
} 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", "supplydepot", "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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) # 追踪资源、人口和军队人口比
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0 # 农民/人口 worker/supply ratio
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口/200 plausible supply (supply/200.0)
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# (人口-现有人口)/人口 population ratio (supply_left/supply)
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 气体/1500 gas/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 晶体矿/1500 minerals minerals/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3) # flip horizontally to make our final fix in visual representation:
self.flipped = cv2.flip(game_data, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) if not HEADLESS:
if self.use_model:
cv2.imshow('Model Intel', resized)
cv2.waitKey(1)
else:
cv2.imshow('Random Intel', resized)
cv2.waitKey(1) def random_location_variance(self, enemy_start_location):
x = enemy_start_location[0]
y = enemy_start_location[1] x += ((random.randrange(-20, 20)) / 100) * self.game_info.map_size[0]
y += ((random.randrange(-20, 20)) / 100) * self.game_info.map_size[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 build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.timeMinutes / 2 and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
# print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造机械台
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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) # 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if len(self.units(UnitTypeId.STARGATE)) < self.timeMinutes:
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon) ## 造兵
async def build_offensive_force(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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0: if self.use_model:
prediction = self.model.predict([self.flipped.reshape(-1, 176, 200, 3)])
choice = np.argmax(prediction[0]) choice_dict = {0: "No Attack!",
1: "Attack close to our nexus!",
2: "Attack Enemy Structure!",
3: "Attack Enemy Start!"}
print("Choice #{}:{}".format(choice, choice_dict[choice]))
else:
choice = random.randrange(0, 4) target = False
if self.timeMinutes > self.do_something_after:
if choice == 0:
# 什么都不做
wait = random.randrange(7, 100) / 100
self.do_something_after = self.timeMinutes + 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] if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped]) ## 启动游戏
# for i in range(50):
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot(use_model=True)),
Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

侦察优化

侦察需要优化的三个点:

1.在ob建造前,使用农民去侦察

2.使用多个侦察单位

3.在待开矿的区域放置侦察单位

保存当前侦察位置

    def __init__(self, use_model=False):
...
self.scouts_and_spots = {}

侦察敌方出生点位附近位置,即敌方开矿、扩张的位置

    async def scout(self):
# {DISTANCE_TO_ENEMY_START:EXPANSIONLOC}
self.expand_dis_dir = {}

expand_dis_dir字典中key是到出生点位的距离,value是实际位置

        for el in self.expansion_locations:
distance_to_enemy_start = el.distance_to(self.enemy_start_locations[0])
#print(distance_to_enemy_start)
self.expand_dis_dir[distance_to_enemy_start] = el

对expand_dis_dir的key排序

self.ordered_exp_distances = sorted(k for k in self.expand_dis_dir)

我们的侦察单位会经常被杀,所以我们要时刻检查侦察单位是否存在

        existing_ids = [unit.tag for unit in self.units]
# 删除已经死亡的侦察单位
to_be_removed = []
for noted_scout in self.scouts_and_spots:
if noted_scout in existing_ids:
to_be_removed.append(noted_scout) for scout in to_be_removed:
del self.scouts_and_spots[scout]

紧随其后,加上如下判断。如果没有ob,使用农民侦察

        if len(self.units(ROBOTICSFACILITY).ready) == 0:
unit_type = PROBE
unit_limit = 1
else:
unit_type = OBSERVER
unit_limit = 15

只需要一个农民侦察,其余采矿、采气

        assign_scout = True

        if unit_type == PROBE:
for unit in self.units(PROBE):
if unit.tag in self.scouts_and_spots:
assign_scout = False

分配侦察任务

至少有一个单位处在空闲状态,然后根据unit_limit迭代该单位类型,再检查unit's tag是否在我们self.scouts_and_spots字典中,最终遍历我们已经排序好的ordered_exp_distances字典。

对于每一个距离,我们查询location是否我们正在侦察的位置,如果不是,我们分配侦察单位侦察任务。如果侦察任务已指派,continue,未指派则break

        if assign_scout:
if len(self.units(unit_type).idle) > 0:
for obs in self.units(unit_type).idle[:unit_limit]:
if obs.tag not in self.scouts_and_spots:
for dist in self.ordered_exp_distances:
try:
location = next(value for key, value in self.expand_dis_dir.items() if key == dist)
# DICT {UNIT_ID:LOCATION}
active_locations = [self.scouts_and_spots[k] for k in self.scouts_and_spots] if location not in active_locations:
if unit_type == PROBE:
for unit in self.units(PROBE):
if unit.tag in self.scouts_and_spots:
continue await self.do(obs.move(location))
self.scouts_and_spots[obs.tag] = location
break
except Exception as e:
pass

防止去侦察的农民去采矿

        for obs in self.units(unit_type):
if obs.tag in self.scouts_and_spots:
if obs in [probe for probe in self.units(UnitTypeId.PROBE)]:
await self.do(obs.move(self.random_location_variance(self.scouts_and_spots[obs.tag])))

random_location_variance方法修改

    def random_location_variance(self, location):
x = location[0]
y = location[1] # FIXED THIS
x += random.randrange(-5,5)
y += random.randrange(-5,5) if x < 0:
print("x below")
x = 0
if y < 0:
print("y below")
y = 0
if x > self.game_info.map_size[0]:
print("x above")
x = self.game_info.map_size[0]
if y > self.game_info.map_size[1]:
print("y above")
y = self.game_info.map_size[1] go_to = position.Point2(position.Pointlike((x,y))) return go_to

添加build_scout方法

    async def build_scout(self):
if len(self.units(OBSERVER)) < math.floor(self.time/3):
for rf in self.units(ROBOTICSFACILITY).ready.noqueue:
print(len(self.units(OBSERVER)), self.time/3)
if self.can_afford(OBSERVER) and self.supply_left > 0:
await self.do(rf.train(OBSERVER))

在on_step方法加上await self.build_scout()

完整代码如下

# -*- encoding: utf-8 -*-
'''
@File : demo.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/11/3 12:32 Jonas None
'''
import math
import os
import time import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result
from sc2.player import Bot, Computer
from sc2.constants import *
import random
import numpy as np
import cv2
import keras HEADLESS = False # os.environ["SC2PATH"] = 'F:\StarCraft II' class SentdeBot(sc2.BotAI):
def __init__(self, use_model=False):
# 经过计算,每分钟大约165迭代次数
# self.ITERATIONS_PER_MINUTE = 165
# 最大农民数量
self.MAX_WORKERS = 50
self.do_something_after = 0
self.train_data = []
self.use_model = use_model
self.scouts_and_spots = {} if self.use_model:
print("use model")
self.model = keras.models.load_model("BasicCNN-30-epochs-0.0001-LR-4.2") # 存储.npy训练数据 def on_end(self, game_result):
print('--- on_end called ---')
print(game_result, self.use_model) with open("log.txt", "a") as f:
if self.use_model:
f.write("Model {}\n".format(game_result))
else:
f.write("Random {}\n".format(game_result)) async def on_step(self, iteration):
# self.iteration = iteration
self.timeMinutes = ((self.state.game_loop / 22.4) / 60) # print('Time:', self.timeMinutes)
await self.build_scout()
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.intel()
await self.attack() async def build_scout(self):
if len(self.units(UnitTypeId.OBSERVER)) < math.floor(self.time / 3):
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
print(len(self.units(UnitTypeId.OBSERVER)), self.time / 3)
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER)) ## 侦察
async def scout(self):
# {DISTANCE_TO_ENEMY_START:EXPANSIONLOC}
self.expand_dis_dir = {}
for el in self.expansion_locations:
distance_to_enemy_start = el.distance_to(self.enemy_start_locations[0])
self.expand_dis_dir[distance_to_enemy_start] = el self.ordered_exp_distances = sorted(k for k in self.expand_dis_dir) existing_ids = [unit.tag for unit in self.units]
# 删除已经死亡的侦察单位
to_be_removed = []
for noted_scout in self.scouts_and_spots:
if noted_scout in existing_ids:
to_be_removed.append(noted_scout) for scout in to_be_removed:
del self.scouts_and_spots[scout] if len(self.units(UnitTypeId.ROBOTICSFACILITY).ready) == 0:
unit_type = UnitTypeId.PROBE
unit_limit = 1
else:
unit_type = UnitTypeId.OBSERVER
unit_limit = 15 assign_scout = True if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spots:
assign_scout = False # 分配侦察任务
if assign_scout:
if len(self.units(unit_type).idle) > 0:
for obs in self.units(unit_type).idle[:unit_limit]:
if obs.tag not in self.scouts_and_spots:
for dist in self.ordered_exp_distances:
try:
location = next(value for key, value in self.expand_dis_dir.items() if key == dist)
# DICT {UNIT_ID:LOCATION}
active_locations = [self.scouts_and_spouts[k] for k in self.scouts_and_spots] if location not in active_locations:
if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spouts:
continue
await self.do(obs.move(location))
self.scouts_and_spouts[obs.tag] = location
break
except Exception as e:
pass # 防止去侦察的农民采矿
for obs in self.units(unit_type):
if obs.tag in self.scouts_and_spots:
if obs in [probe for probe in self.units(UnitTypeId.PROBE)]:
await self.do(obs.move(self.random_location_variance(self.scouts_and_spots[obs.tag]))) async def intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) # UnitTypeId作为key,半径和颜色是value
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.ROBOTICSFACILITY: [5, (215, 155, 0)], UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
# OBSERVER: [3, (255, 255, 255)],
} 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', 'orbitalcommand', 'planetaryfortress', '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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) # 追踪资源、人口和军队人口比
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
if military_weight > 1.0:
military_weight = 1.0 # 农民/人口 worker/supply ratio
cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
# 人口/200 plausible supply (supply/200.0)
cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
# (人口-现有人口)/人口 population ratio (supply_left/supply)
cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
# 气体/1500 gas/1500
cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
# 晶体矿/1500 minerals minerals/1500
cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3) # flip horizontally to make our final fix in visual representation:
self.flipped = cv2.flip(game_data, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) if not HEADLESS:
if self.use_model:
cv2.imshow('Model Intel', resized)
cv2.waitKey(1)
else:
cv2.imshow('Random Intel', resized)
cv2.waitKey(1) def random_location_variance(self, enemy_start_location):
x = enemy_start_location[0]
y = enemy_start_location[1] x += random.randrange(-5, 5)
y += random.randrange(-5, 5) 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 build_workers(self):
# 星灵枢钮*16(一个基地配备16个农民)大于农民数量并且现有农民数量小于MAX_WORKERS
if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
# 星灵枢纽(NEXUS)无队列建造,可以提高晶体矿的利用率,不至于占用资源
for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
# 是否有50晶体矿建造农民
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 build_assimilators(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
# 在瓦斯泉上建造吸收厂
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) ## 开矿
async def expand(self):
# (self.iteration / self.ITERATIONS_PER_MINUTE)是一个缓慢递增的值,动态开矿
if self.units(UnitTypeId.NEXUS).amount < self.timeMinutes / 2 and self.can_afford(
UnitTypeId.NEXUS):
await self.expand_now() ## 建造进攻性建筑
async def offensive_force_buildings(self):
# print(self.iteration / self.ITERATIONS_PER_MINUTE)
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
# 根据神族建筑科技图,折跃门建造过后才可以建造控制核心
if self.units(UnitTypeId.GATEWAY).ready.exists and 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)
# 否则建造折跃门
# (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一个缓慢递增的值
# elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
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)
# 控制核心存在的情况下建造机械台
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
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) # 控制核心存在的情况下建造星门
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if len(self.units(UnitTypeId.STARGATE)) < self.timeMinutes:
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon) ## 造兵
async def build_offensive_force(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)) ## 寻找目标
def find_target(self, state):
if len(self.known_enemy_units) > 0:
# 随机选取敌方单位
return random.choice(self.known_enemy_units)
elif len(self.known_enemy_units) > 0:
# 随机选取敌方建筑
return random.choice(self.known_enemy_structures)
else:
# 返回敌方出生点位
return self.enemy_start_locations[0] ## 进攻
async def attack(self):
if len(self.units(UnitTypeId.VOIDRAY).idle) > 0: if self.use_model:
prediction = self.model.predict([self.flipped.reshape(-1, 176, 200, 3)])
choice = np.argmax(prediction[0]) choice_dict = {0: "No Attack!",
1: "Attack close to our nexus!",
2: "Attack Enemy Structure!",
3: "Attack Enemy Start!"}
print("Choice #{}:{}".format(choice, choice_dict[choice]))
else:
choice = random.randrange(0, 4) target = False
if self.timeMinutes > self.do_something_after:
if choice == 0:
# 什么都不做
wait = random.randrange(7, 100) / 100
self.do_something_after = self.timeMinutes + 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] if target:
for vr in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
self.train_data.append([y, self.flipped]) ## 启动游戏
# for i in range(50):
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot(use_model=True)),
Computer(Race.Terran, Difficulty.Medium)
], realtime=False)

运行结果如下,可以看到,农民到敌方基地侦察了。

增加选择

choice的字典如下:

        self.choices = {0: self.build_scout,
1: self.build_zealot,
2: self.build_gateway,
3: self.build_voidray,
4: self.build_stalker,
5: self.build_worker,
6: self.build_assimilator,
7: self.build_stargate,
8: self.build_pylon,
9: self.defend_nexus,
10: self.attack_known_enemy_unit,
11: self.attack_known_enemy_structure,
12: self.expand,
13: self.do_nothing,
}

on_step方法中删除多余的调用方法

    async def on_step(self, iteration):

        self.timeMinutes = (self.state.game_loop/22.4) / 60
#print('Time:',self.time)
await self.distribute_workers()
await self.scout()
await self.intel()
await self.do_something()

创建do_something方法

    async def do_something(self):

        if self.time > self.do_something_after:
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
else:
choice = random.randrange(0, 14)
try:
await self.choices[choice]()
except Exception as e:
print(str(e))
y = np.zeros(14)
y[choice] = 1
self.train_data.append([y, self.flipped])

实现on_step中的方法

训练侦察单位

    async def build_scout(self):
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
print(len(self.units(UnitTypeId.OBSERVER)), self.timeMinutes/3)
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
break

训练狂热者

    async def build_zealot(self):
gateways = self.units(UnitTypeId.GATEWAY).ready
if gateways.exists:
if self.can_afford(UnitTypeId.ZEALOT):
await self.do(random.choice(gateways).train(UnitTypeId.ZEALOT))

建造折跃门

    async def build_gateway(self):
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon)

训练虚空战舰

    async def build_voidray(self):
stargates = self.units(UnitTypeId.STARGATE).ready
if stargates.exists:
if self.can_afford(UnitTypeId.VOIDRAY):
await self.do(random.choice(stargates).train(UnitTypeId.VOIDRAY))

训练追猎者

    async def build_stalker(self):
pylon = self.units(UnitTypeId.PYLON).ready.random
gateways = self.units(UnitTypeId.GATEWAY).ready
cybernetics_cores = self.units(UnitTypeId.CYBERNETICSCORE).ready if gateways.exists and cybernetics_cores.exists:
if self.can_afford(UnitTypeId.STALKER):
await self.do(random.choice(gateways).train(UnitTypeId.STALKER)) if not cybernetics_cores.exists:
if self.units(UnitTypeId.GATEWAY).ready.exists:
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

训练农民

    async def build_worker(self):
nexuses = self.units(UnitTypeId.NEXUS).ready
if nexuses.exists:
if self.can_afford(UnitTypeId.PROBE):
await self.do(random.choice(nexuses).train(UnitTypeId.PROBE))

建造瓦斯气泉

    async def build_assimilator(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))

建造星门

    async def build_stargate(self):
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon)

建造水晶塔

    async def build_pylon(self):
nexuses = self.units(UnitTypeId.NEXUS).ready
if nexuses.exists:
if self.can_afford(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=self.units(UnitTypeId.NEXUS).first.position.towards(self.game_info.map_center, 5))

扩张、开矿

    async def expand(self):
try:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
except Exception as e:
print(str(e))

四种进攻选择

    async def do_nothing(self):
wait = random.randrange(7, 100)/100
self.do_something_after = self.timeMinutes + wait async def defend_nexus(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_structure(self):
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_unit(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target))

完整代码如下

# -*- encoding: utf-8 -*-
'''
@File : demo2.py
@Modify Time @Author @Desciption
------------ ------- -----------
2019/12/13 10:58 Jonas None
''' import sc2
from sc2 import run_game, maps, Race, Difficulty, Result
from sc2.player import Bot, Computer
from sc2 import position
from sc2.constants import *
import random
import cv2
import numpy as np
import os
import time
import math
import keras HEADLESS = False class SentdeBot(sc2.BotAI):
def __init__(self, use_model=False, title=1):
self.MAX_WORKERS = 50
self.do_something_after = 0
self.use_model = use_model
self.title = title self.scouts_and_spots = {} # ADDED THE CHOICES #
self.choices = {0: self.build_scout,
1: self.build_zealot,
2: self.build_gateway,
3: self.build_voidray,
4: self.build_stalker,
5: self.build_worker,
6: self.build_assimilator,
7: self.build_stargate,
8: self.build_pylon,
9: self.defend_nexus,
10: self.attack_known_enemy_unit,
11: self.attack_known_enemy_structure,
12: self.expand,
13: self.do_nothing,
} self.train_data = []
if self.use_model:
print("USING MODEL!")
self.model = keras.models.load_model("BasicCNN-30-epochs-0.0001-LR-4.2") def on_end(self, game_result):
print('--- on_end called ---')
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, int(time.time())))
else:
f.write("Random {} - {}\n".format(game_result, int(time.time()))) async def on_step(self, iteration): self.timeMinutes = (self.state.game_loop/22.4) / 60
#print('Time:',self.time)
await self.distribute_workers()
await self.scout()
await self.intel()
await self.do_something() def random_location_variance(self, location):
x = location[0]
y = location[1] # FIXED THIS
x += random.randrange(-5,5)
y += random.randrange(-5,5) if x < 0:
print("x below")
x = 0
if y < 0:
print("y below")
y = 0
if x > self.game_info.map_size[0]:
print("x above")
x = self.game_info.map_size[0]
if y > self.game_info.map_size[1]:
print("y above")
y = self.game_info.map_size[1] go_to = position.Point2(position.Pointlike((x,y))) return go_to async def scout(self):
self.expand_dis_dir = {} for el in self.expansion_locations:
distance_to_enemy_start = el.distance_to(self.enemy_start_locations[0])
#print(distance_to_enemy_start)
self.expand_dis_dir[distance_to_enemy_start] = el self.ordered_exp_distances = sorted(k for k in self.expand_dis_dir) existing_ids = [unit.tag for unit in self.units]
# removing of scouts that are actually dead now.
to_be_removed = []
for noted_scout in self.scouts_and_spots:
if noted_scout not in existing_ids:
to_be_removed.append(noted_scout) for scout in to_be_removed:
del self.scouts_and_spots[scout] if len(self.units(UnitTypeId.ROBOTICSFACILITY).ready) == 0:
unit_type = UnitTypeId.PROBE
unit_limit = 1
else:
unit_type = UnitTypeId.OBSERVER
unit_limit = 15 assign_scout = True if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spots:
assign_scout = False if assign_scout:
if len(self.units(unit_type).idle) > 0:
for obs in self.units(unit_type).idle[:unit_limit]:
if obs.tag not in self.scouts_and_spots:
for dist in self.ordered_exp_distances:
try:
location = next(value for key, value in self.expand_dis_dir.items() if key == dist)
# DICT {UNIT_ID:LOCATION}
active_locations = [self.scouts_and_spots[k] for k in self.scouts_and_spots] if location not in active_locations:
if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spots:
continue await self.do(obs.move(location))
self.scouts_and_spots[obs.tag] = location
break
except Exception as e:
pass for obs in self.units(unit_type):
if obs.tag in self.scouts_and_spots:
if obs in [probe for probe in self.units(UnitTypeId.PROBE)]:
await self.do(obs.move(self.random_location_variance(self.scouts_and_spots[obs.tag]))) async def intel(self): 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.ROBOTICSFACILITY: [5, (215, 155, 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) # from Александр Тимофеев via YT
main_base_names = ['nexus', 'commandcenter', 'orbitalcommand', 'planetaryfortress', '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) for obs in self.units(UnitTypeId.OBSERVER).ready:
pos = obs.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1) for vr in self.units(UnitTypeId.VOIDRAY).ready:
pos = vr.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (255, 100, 0), -1) line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 worker_weight = len(self.units(UnitTypeId.PROBE)) / (self.supply_cap-self.supply_left)
if worker_weight > 1.0:
worker_weight = 1.0 cv2.line(game_data, (0, 19), (int(line_max*worker_weight), 19), (250, 250, 200), 3) # worker/supply ratio
cv2.line(game_data, (0, 15), (int(line_max*plausible_supply), 15), (220, 200, 200), 3) # plausible supply (supply/200.0)
cv2.line(game_data, (0, 11), (int(line_max*population_ratio), 11), (150, 150, 150), 3) # population ratio (supply_left/supply)
cv2.line(game_data, (0, 7), (int(line_max*vespene_ratio), 7), (210, 200, 0), 3) # gas / 1500
cv2.line(game_data, (0, 3), (int(line_max*mineral_ratio), 3), (0, 255, 25), 3) # minerals minerals/1500 # flip horizontally to make our final fix in visual representation:
self.flipped = cv2.flip(game_data, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) if not HEADLESS:
cv2.imshow(str(self.title), resized)
cv2.waitKey(1) def find_target(self, state):
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 build_scout(self):
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
print(len(self.units(UnitTypeId.OBSERVER)), self.timeMinutes/3)
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
break async def build_worker(self):
nexuses = self.units(UnitTypeId.NEXUS).ready
if nexuses.exists:
if self.can_afford(UnitTypeId.PROBE):
await self.do(random.choice(nexuses).train(UnitTypeId.PROBE)) async def build_zealot(self):
gateways = self.units(UnitTypeId.GATEWAY).ready
if gateways.exists:
if self.can_afford(UnitTypeId.ZEALOT):
await self.do(random.choice(gateways).train(UnitTypeId.ZEALOT)) async def build_gateway(self):
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon) async def build_voidray(self):
stargates = self.units(UnitTypeId.STARGATE).ready
if stargates.exists:
if self.can_afford(UnitTypeId.VOIDRAY):
await self.do(random.choice(stargates).train(UnitTypeId.VOIDRAY)) async def build_stalker(self):
pylon = self.units(UnitTypeId.PYLON).ready.random
gateways = self.units(UnitTypeId.GATEWAY).ready
cybernetics_cores = self.units(UnitTypeId.CYBERNETICSCORE).ready if gateways.exists and cybernetics_cores.exists:
if self.can_afford(UnitTypeId.STALKER):
await self.do(random.choice(gateways).train(UnitTypeId.STALKER)) if not cybernetics_cores.exists:
if self.units(UnitTypeId.GATEWAY).ready.exists:
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon) async def build_assimilator(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) async def build_stargate(self):
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon) async def build_pylon(self):
nexuses = self.units(UnitTypeId.NEXUS).ready
if nexuses.exists:
if self.can_afford(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=self.units(UnitTypeId.NEXUS).first.position.towards(self.game_info.map_center, 5)) async def expand(self):
try:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
except Exception as e:
print(str(e)) async def do_nothing(self):
wait = random.randrange(7, 100)/100
self.do_something_after = self.timeMinutes + wait async def defend_nexus(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_structure(self):
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_unit(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def do_something(self): if self.timeMinutes > self.do_something_after:
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
else:
choice = random.randrange(0, 14)
try:
await self.choices[choice]()
except Exception as e:
print(str(e))
###### NEW CHOICE HANDLING HERE #########
###### NEW CHOICE HANDLING HERE #########
y = np.zeros(14)
y[choice] = 1
self.train_data.append([y, self.flipped]) if True:
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot(use_model=False, title=1)),
Computer(Race.Terran, Difficulty.Medium),
], realtime=False)

运行结果如下

统计图优化

将彩色改成黑白,突出显示单位

建立三阶矩阵

    async def intel(self):

        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

迭代当前单位,绘制圆圈,我方单位白色

        for unit in self.units().ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius*8), (255, 255, 255), math.ceil(int(unit.radius*0.5)))

迭代敌人,绘制圆圈,敌方单位灰色,

        for unit in self.known_enemy_units:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius*8), (125, 125, 125), math.ceil(int(unit.radius*0.5)))

绘制统计资源的直线

        try:
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 worker_weight = len(self.units(PROBE)) / (self.supply_cap-self.supply_left)
if worker_weight > 1.0:
worker_weight = 1.0 cv2.line(game_data, (0, 19), (int(line_max*worker_weight), 19), (250, 250, 200), 3) # worker/supply ratio
cv2.line(game_data, (0, 15), (int(line_max*plausible_supply), 15), (220, 200, 200), 3) # plausible supply (supply/200.0)
cv2.line(game_data, (0, 11), (int(line_max*population_ratio), 11), (150, 150, 150), 3) # population ratio (supply_left/supply)
cv2.line(game_data, (0, 7), (int(line_max*vespene_ratio), 7), (210, 200, 0), 3) # gas / 1500
cv2.line(game_data, (0, 3), (int(line_max*mineral_ratio), 3), (0, 255, 25), 3) # minerals minerals/1500
except Exception as e:
print(str(e))

resize和flipp

        # flip horizontally to make our final fix in visual representation:
grayed = cv2.cvtColor(game_data, cv2.COLOR_BGR2GRAY)
self.flipped = cv2.flip(grayed, 0)
resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
if not HEADLESS:
cv2.imshow(str(self.title), resized)
cv2.waitKey(1)

在do_something方法可以手动设置单位建造的权重

    async def do_something(self):

        if self.time > self.do_something_after:
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
else:
worker_weight = 8
zealot_weight = 3
voidray_weight = 20
stalker_weight = 8
pylon_weight = 5
stargate_weight = 5
gateway_weight = 3 choice_weights = 1*[0]+zealot_weight*[1]+gateway_weight*[2]+voidray_weight*[3]+stalker_weight*[4]+worker_weight*[5]+1*[6]+stargate_weight*[7]+pylon_weight*[8]+1*[9]+1*[10]+1*[11]+1*[12]+1*[13]
choice = random.choice(choice_weights)
try:
await self.choices[choice]()
except Exception as e:
print(str(e)) y = np.zeros(14)
y[choice] = 1
self.train_data.append([y, self.flipped])

至此,完整代码如下:

import sc2
from sc2 import run_game, maps, Race, Difficulty, Result
from sc2.player import Bot, Computer
from sc2 import position
from sc2.constants import *
import random
import cv2
import numpy as np
import os
import time
import math
import keras # os.environ["SC2PATH"] = '/starcraftstuff/StarCraftII/'
HEADLESS = True class SentdeBot(sc2.BotAI):
def __init__(self, use_model=False, title=1):
self.MAX_WORKERS = 50
self.do_something_after = 0
self.use_model = use_model
self.title = title
# DICT {UNIT_ID:LOCATION}
# every iteration, make sure that unit id still exists!
self.scouts_and_spots = {} # ADDED THE CHOICES #
self.choices = {0: self.build_scout,
1: self.build_zealot,
2: self.build_gateway,
3: self.build_voidray,
4: self.build_stalker,
5: self.build_worker,
6: self.build_assimilator,
7: self.build_stargate,
8: self.build_pylon,
9: self.defend_nexus,
10: self.attack_known_enemy_unit,
11: self.attack_known_enemy_structure,
12: self.expand, # might just be self.expand_now() lol
13: self.do_nothing,
} self.train_data = []
if self.use_model:
print("USING MODEL!")
self.model = keras.models.load_model("BasicCNN-30-epochs-0.0001-LR-4.2") def on_end(self, game_result):
print('--- on_end called ---')
print(game_result, self.use_model)
#if self.timeMinutes < 17:
if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data)) async def on_step(self, iteration): self.timeMinutes = (self.state.game_loop/22.4) / 60
print('Time:',self.timeMinutes) if iteration % 5 == 0:
await self.distribute_workers()
await self.scout()
await self.intel()
await self.do_something() def random_location_variance(self, location):
x = location[0]
y = location[1] # FIXED THIS
x += random.randrange(-5,5)
y += random.randrange(-5,5) if x < 0:
print("x below")
x = 0
if y < 0:
print("y below")
y = 0
if x > self.game_info.map_size[0]:
print("x above")
x = self.game_info.map_size[0]
if y > self.game_info.map_size[1]:
print("y above")
y = self.game_info.map_size[1] go_to = position.Point2(position.Pointlike((x,y))) return go_to async def scout(self):
self.expand_dis_dir = {} for el in self.expansion_locations:
distance_to_enemy_start = el.distance_to(self.enemy_start_locations[0])
#print(distance_to_enemy_start)
self.expand_dis_dir[distance_to_enemy_start] = el self.ordered_exp_distances = sorted(k for k in self.expand_dis_dir) existing_ids = [unit.tag for unit in self.units]
# removing of scouts that are actually dead now.
to_be_removed = []
for noted_scout in self.scouts_and_spots:
if noted_scout not in existing_ids:
to_be_removed.append(noted_scout) for scout in to_be_removed:
del self.scouts_and_spots[scout] if len(self.units(UnitTypeId.ROBOTICSFACILITY).ready) == 0:
unit_type = UnitTypeId.PROBE
unit_limit = 1
else:
unit_type = UnitTypeId.OBSERVER
unit_limit = 15 assign_scout = True if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spots:
assign_scout = False if assign_scout:
if len(self.units(unit_type).idle) > 0:
for obs in self.units(unit_type).idle[:unit_limit]:
if obs.tag not in self.scouts_and_spots:
for dist in self.ordered_exp_distances:
try:
location = next(value for key, value in self.expand_dis_dir.items() if key == dist)
# DICT {UNIT_ID:LOCATION}
active_locations = [self.scouts_and_spots[k] for k in self.scouts_and_spots] if location not in active_locations:
if unit_type == UnitTypeId.PROBE:
for unit in self.units(UnitTypeId.PROBE):
if unit.tag in self.scouts_and_spots:
continue await self.do(obs.move(location))
self.scouts_and_spots[obs.tag] = location
break
except Exception as e:
pass for obs in self.units(unit_type):
if obs.tag in self.scouts_and_spots:
if obs in [probe for probe in self.units(UnitTypeId.PROBE)]:
await self.do(obs.move(self.random_location_variance(self.scouts_and_spots[obs.tag]))) async def intel(self): game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8) for unit in self.units().ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius*8), (255, 255, 255), math.ceil(int(unit.radius*0.5))) for unit in self.known_enemy_units:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius*8), (125, 125, 125), math.ceil(int(unit.radius*0.5))) try:
line_max = 50
mineral_ratio = self.minerals / 1500
if mineral_ratio > 1.0:
mineral_ratio = 1.0 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 plausible_supply = self.supply_cap / 200.0 worker_weight = len(self.units(UnitTypeId.PROBE)) / (self.supply_cap-self.supply_left)
if worker_weight > 1.0:
worker_weight = 1.0 cv2.line(game_data, (0, 19), (int(line_max*worker_weight), 19), (250, 250, 200), 3) # worker/supply ratio
cv2.line(game_data, (0, 15), (int(line_max*plausible_supply), 15), (220, 200, 200), 3) # plausible supply (supply/200.0)
cv2.line(game_data, (0, 11), (int(line_max*population_ratio), 11), (150, 150, 150), 3) # population ratio (supply_left/supply)
cv2.line(game_data, (0, 7), (int(line_max*vespene_ratio), 7), (210, 200, 0), 3) # gas / 1500
cv2.line(game_data, (0, 3), (int(line_max*mineral_ratio), 3), (0, 255, 25), 3) # minerals minerals/1500
except Exception as e:
print(str(e)) # flip horizontally to make our final fix in visual representation:
grayed = cv2.cvtColor(game_data, cv2.COLOR_BGR2GRAY)
self.flipped = cv2.flip(grayed, 0) resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2) if not HEADLESS:
if self.use_model:
cv2.imshow(str(self.title), resized)
cv2.waitKey(1)
else:
cv2.imshow(str(self.title), resized)
cv2.waitKey(1) def find_target(self, state):
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 build_scout(self):
for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
print(len(self.units(UnitTypeId.OBSERVER)), self.timeMinutes/3)
if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
await self.do(rf.train(UnitTypeId.OBSERVER))
break
if len(self.units(UnitTypeId.ROBOTICSFACILITY)) == 0:
pylon = self.units(UnitTypeId.PYLON).ready.noqueue.random
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon) async def build_worker(self):
nexuses = self.units(UnitTypeId.NEXUS).ready.noqueue
if nexuses.exists:
if self.can_afford(UnitTypeId.PROBE):
await self.do(random.choice(nexuses).train(UnitTypeId.PROBE)) async def build_zealot(self):
#if len(self.units(ZEALOT)) < (8 - self.timeMinutes): # how we can phase out zealots over time?
gateways = self.units(UnitTypeId.GATEWAY).ready.noqueue
if gateways.exists:
if self.can_afford(UnitTypeId.ZEALOT):
await self.do(random.choice(gateways).train(UnitTypeId.ZEALOT)) async def build_gateway(self):
#if len(self.units(GATEWAY)) < 5:
pylon = self.units(UnitTypeId.PYLON).ready.noqueue.random
if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
await self.build(UnitTypeId.GATEWAY, near=pylon.position.towards(self.game_info.map_center, 5)) async def build_voidray(self):
stargates = self.units(UnitTypeId.STARGATE).ready.noqueue
if stargates.exists:
if self.can_afford(UnitTypeId.VOIDRAY):
await self.do(random.choice(stargates).train(UnitTypeId.VOIDRAY)) async def build_stalker(self):
pylon = self.units(UnitTypeId.PYLON).ready.noqueue.random
gateways = self.units(UnitTypeId.GATEWAY).ready
cybernetics_cores = self.units(UnitTypeId.CYBERNETICSCORE).ready if gateways.exists and cybernetics_cores.exists:
if self.can_afford(UnitTypeId.STALKER):
await self.do(random.choice(gateways).train(UnitTypeId.STALKER)) if not cybernetics_cores.exists:
if self.units(UnitTypeId.GATEWAY).ready.exists:
if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon.position.towards(self.game_info.map_center, 5)) async def build_assimilator(self):
for nexus in self.units(UnitTypeId.NEXUS).ready:
vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
for vaspene in vaspenes:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vaspene.position)
if worker is None:
break
if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene)) async def build_stargate(self):
if self.units(UnitTypeId.PYLON).ready.exists:
pylon = self.units(UnitTypeId.PYLON).ready.random
if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
await self.build(UnitTypeId.STARGATE, near=pylon.position.towards(self.game_info.map_center, 5)) async def build_pylon(self):
nexuses = self.units(UnitTypeId.NEXUS).ready
if nexuses.exists:
if self.can_afford(UnitTypeId.PYLON) and not self.already_pending(UnitTypeId.PYLON):
await self.build(UnitTypeId.PYLON, near=self.units(UnitTypeId.NEXUS).first.position.towards(self.game_info.map_center, 5)) async def expand(self):
try:
if self.can_afford(UnitTypeId.NEXUS) and len(self.units(UnitTypeId.NEXUS)) < 3:
await self.expand_now()
except Exception as e:
print(str(e)) async def do_nothing(self):
wait = random.randrange(7, 100)/100
self.do_something_after = self.timeMinutes + wait async def defend_nexus(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_structure(self):
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def attack_known_enemy_unit(self):
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
for u in self.units(UnitTypeId.VOIDRAY).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.STALKER).idle:
await self.do(u.attack(target))
for u in self.units(UnitTypeId.ZEALOT).idle:
await self.do(u.attack(target)) async def do_something(self): if self.timeMinutes > self.do_something_after:
if self.use_model:
prediction = self.model.predict([self.flipped.reshape([-1, 176, 200, 3])])
choice = np.argmax(prediction[0])
else:
worker_weight = 8
zealot_weight = 3
voidray_weight = 20
stalker_weight = 8
pylon_weight = 5
stargate_weight = 5
gateway_weight = 3 choice_weights = 1*[0]+zealot_weight*[1]+gateway_weight*[2]+voidray_weight*[3]+stalker_weight*[4]+worker_weight*[5]+1*[6]+stargate_weight*[7]+pylon_weight*[8]+1*[9]+1*[10]+1*[11]+1*[12]+1*[13]
choice = random.choice(choice_weights) try:
await self.choices[choice]()
except Exception as e:
print(str(e)) y = np.zeros(14)
y[choice] = 1
self.train_data.append([y, self.flipped]) while True:
run_game(maps.get("AbyssalReefLE"), [
Bot(Race.Protoss, SentdeBot()),
#Bot(Race.Protoss, SentdeBot()),
Computer(Race.Protoss, Difficulty.Easy)
], realtime=False)

运行结果如下

参考链接

源码:源代码

教程:教程生肉

星际争霸2 AI开发(持续更新)的更多相关文章

  1. FaceBook 发布星际争霸最大 AI 数据集

    简介 我们刚发布了最大的星际争霸:Brood War 重播数据集,有 65646 个游戏.完整的数据集经过压缩之后有 365 GB,1535 million 帧,和 496 million 操作动作. ...

  2. iOS-BLE蓝牙开发持续更新

    文/煜寒了(简书作者)原文链接:http://www.jianshu.com/p/84b5b834b942著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 在写这个博客之前,空余时间抽看 ...

  3. 星际争霸的虫王IA退役2年搞AI,自叹不如了

    ------------恢复内容开始------------ 金磊 发自 凹非寺 量子位|公众号 QbitA 这年头,直播讲AI,真算不上什么新鲜事.但要是连职业电竞选手,都开播主讲呢?没开玩笑,是真 ...

  4. 【转载】 星际争霸2的AI环境搭建

    原文地址: https://blog.csdn.net/qq_40244666/article/details/80957644 作者:BOY_IT_IT 来源:CSDN -------------- ...

  5. 人类又被AI碾压,这次是星际争霸

    还记得2017年,那个血洗围棋界的“阿尔法狗”吗?     这个由谷歌旗下 DeepMind 公司开发的 AI ,对阵世界顶尖围棋选手,打出完全碾压式的战绩: AlphaGo vs. 樊麾 - 5 : ...

  6. 2018年星际争霸AI挑战赛–三星与FB获冠亚军,中科院自动化所夺得季军

    雷锋网 AI 科技评论消息,2018 年 11 月 13-17 日,AAAI 人工智能与交互式数字娱乐大会 (AI for Interactive Digital Entertainment) 在阿尔 ...

  7. iOS开发系列文章(持续更新……)

    iOS开发系列的文章,内容循序渐进,包含C语言.ObjC.iOS开发以及日后要写的游戏开发和Swift编程几部分内容.文章会持续更新,希望大家多多关注,如果文章对你有帮助请点赞支持,多谢! 为了方便大 ...

  8. 基于android studio的快捷开发(将持续更新)

    对于Android studio作为谷歌公司的亲儿子,自然有它的好用的地方,特别是gradle方式和快捷提示方式真的很棒.下面是我在实际开发中一些比较喜欢用的快速开发快捷键,对于基本的那些就不多说了. ...

  9. 关于ASP.NET MVC开发设计中出现的问题与解决方案汇总 【持续更新】

    最近一直用ASP.NET MVC 4.0 +LINQ TO SQL来开发设计公司内部多个业务系统网站,在这其中发现了一些问题,也花了不少时间来查找相关资料或请教高人,最终都还算解决了,现在我将这些问题 ...

随机推荐

  1. 本月16日SpringBoot2.2发布,有哪些变化先知晓

    本月(2019年10月16日)Spring Boot 2.2已经正式发布了!在此篇文章中,将给大家介绍一下2.2版为大家带来了哪些重要的新变化.笔者用心书写,希望阅读完成之后转发关注,你的支持是我不竭 ...

  2. 百万年薪python之路 -- 文件操作

    1.文件操作: f = open("zcy.txt" , mode="r" , encoding="UTF-8") open() 打开 第一 ...

  3. Map文件从IDA到OD

    目录 什么是map文件 IDA与OD导出使用map文件 注意事项 使用OD载入导出的map文件 什么是map文件 什么是 MAP 文件? 简单地讲, MAP 文件是程序的全局符号.源文件和代码行号信息 ...

  4. VMware问题--无法获得 VMCI 驱动程序的版本: 句柄无效

    关于VMware问题:无法获得 VMCI 驱动程序的版本: 句柄无效.驱动程序“vmci.sys”的版本不正确 问题截图: 解决 1.根据配置文件路径找到对应的.vmx文件: 2.用编辑器打开,找到v ...

  5. 20190906_matplotlib_学习与快速实现

    20190906 Matplotlib 学习总结 第一部分: 参考连接: Introduction to Matplotlib and basic line https://www.jianshu.c ...

  6. 泛微e-cology OA系统远程代码执行漏洞及其复现

    泛微e-cology OA系统远程代码执行漏洞及其复现 2019年9月19日,泛微e-cology OA系统自带BeanShell组件被爆出存在远程代码执行漏洞.攻击者通过调用BeanShell组件中 ...

  7. python的位置参数、关键字参数、收集参数,关键字收集参数混合调用问题

    参数混合调用顺序用法: 函数中参数顺序为:普通参数,收集参数,关键字参数,关键字收集参数,其顺序不能颠倒,颠倒会报错. 普通参数.关键字参数可以有n个,对量没有具体要求,收集参数和关键字收集参数要么没 ...

  8. 记录一次C#的asyn和await

    static void Main(string[] args) { var d = new NavDownLoader(); Task<bool> success = d.DownLoad ...

  9. [tesseract-ocr]OCR图像识别Ubuntu下环境包安装

    问题: ImportError: libSM.so.6: cannot open shared object file: No such file or directory 解决: sudo apt- ...

  10. [Python]python面向对象 __new__方法及单例设计

    __new__ 方法 使用 类名() 创建对象时,Python 的解释器 首先 会 调用 __new__ 方法为对象 分配空间 __new__ 是一个 由 object 基类提供的 内置的静态方法,主 ...