Python深度学习SC2(星际争霸2)AI

1.准备工作

战网下载sc2
python3(这里用python3.9)
下载游戏地图,前往Blizzard s2client的“ 地图包”部分并下载一些地图。
https://github.com/Blizzard/s2client-proto#map-packs
在这里插入图片描述
下载好后将地图放入SC2的安装目录的Maps下(没有Maps则新建)结构为:
在这里插入图片描述

为了方便以星灵种族为例:
游戏内容参考资料 https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)
Protoss
在这里插入图片描述

安装sc2包 pip3 install sc2

如果sc2是自定义安装:
打开sc2内的paths.py(位于C:\Users***\AppData\Local\Programs\Python\Python37\Lib\site-packages\sc2\paths.py)

BASEDIR = {
“Windows”: “C:/Program Files (x86)/StarCraft II”, #更改匹配路径
“Darwin”: “/Applications/StarCraft II”,
“Linux”: “~/StarCraftII”,
“WineLinux”: “~/.wine/drive_c/Program Files (x86)/StarCraft II”, }

创建一个文件

创建一个py文件如test.py

导入sc2包
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty    #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer    #Bot是将要编写的AI,Computer是对战的电脑

也可以只写第一行,这样写只是为了方便

自定义一个类
"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
	#用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #每一步做什么
        await self.distribute_workers()         #父类中的方法

开局有12名工人,首先给他们平均分配工作,将工作人员分配到所有基地。sc2.BotAI父类有一个名为的方法distribute_workers正好可以实现这个功能(如果觉得不够精细,后面也可以自己写分发功能)

想要了解如何实现的可以看这

async def distribute_workers(self, resource_ratio: float = 2):
        """
        Distributes workers across all the bases taken.
        Keyword `resource_ratio` takes a float. If the current minerals to gas
        ratio is bigger than `resource_ratio`, this function prefer filling geysers
        first, if it is lower, it will prefer sending workers to minerals first.
        This is only for workers that need to be moved anyways, it will NOT will
        geysers on its own.

        NOTE: This function is far from optimal, if you really want to have
        refined worker control, you should write your own distribution function.
        For example long distance mining control and moving workers if a base was killed
        are not being handled.

        WARNING: This is quite slow when there are lots of workers or multiple bases.
        """
        if not self.state.mineral_field or not self.workers or not self.townhalls.ready:
            return
        actions = []
        worker_pool = [worker for worker in self.workers.idle]
        bases = self.townhalls.ready
        geysers = self.geysers.ready

        # list of places that need more workers
        deficit_mining_places = []

        for mining_place in bases | geysers:
            difference = mining_place.surplus_harvesters
            # perfect amount of workers, skip mining place
            if not difference:
                continue
            if mining_place.is_vespene_geyser:
                # get all workers that target the gas extraction site
                # or are on their way back from it
                local_workers = self.workers.filter(
                    lambda unit: unit.order_target == mining_place.tag
                    or (unit.is_carrying_vespene and unit.order_target == bases.closest_to(mining_place).tag)
                )
            else:
                # get tags of minerals around expansion
                local_minerals_tags = {
                    mineral.tag for mineral in self.state.mineral_field if mineral.distance_to(mining_place) <= 8
                }
                # get all target tags a worker can have
                # tags of the minerals he could mine at that base
                # get workers that work at that gather site
                local_workers = self.workers.filter(
                    lambda unit: unit.order_target in local_minerals_tags
                    or (unit.is_carrying_minerals and unit.order_target == mining_place.tag)
                )
            # too many workers
            if difference > 0:
                for worker in local_workers[:difference]:
                    worker_pool.append(worker)
            # too few workers
            # add mining place to deficit bases for every missing worker
            else:
                deficit_mining_places += [mining_place for _ in range(-difference)]

        # prepare all minerals near a base if we have too many workers
        # and need to send them to the closest patch
        if len(worker_pool) > len(deficit_mining_places):
            all_minerals_near_base = [
                mineral
                for mineral in self.state.mineral_field
                if any(mineral.distance_to(base) <= 8 for base in self.townhalls.ready)
            ]
        # distribute every worker in the pool
        for worker in worker_pool:
            # as long as have workers and mining places
            if deficit_mining_places:
                # choose only mineral fields first if current mineral to gas ratio is less than target ratio
                if self.vespene and self.minerals / self.vespene < resource_ratio:
                    possible_mining_places = [place for place in deficit_mining_places if not place.vespene_contents]
                # else prefer gas
                else:
                    possible_mining_places = [place for place in deficit_mining_places if place.vespene_contents]
                # if preferred type is not available any more, get all other places
                if not possible_mining_places:
                    possible_mining_places = deficit_mining_places
                # find closest mining place
                current_place = min(deficit_mining_places, key=lambda place: place.distance_to(worker))
                # remove it from the list
                deficit_mining_places.remove(current_place)
                # if current place is a gas extraction site, go there
                if current_place.vespene_contents:
                    actions.append(worker.gather(current_place))
                # if current place is a gas extraction site,
                # go to the mineral field that is near and has the most minerals left
                else:
                    local_minerals = [
                        mineral for mineral in self.state.mineral_field if mineral.distance_to(current_place) <= 8
                    ]
                    target_mineral = max(local_minerals, key=lambda mineral: mineral.mineral_contents)
                    actions.append(worker.gather(target_mineral))
            # more workers to distribute than free mining spots
            # send to closest if worker is doing nothing
            elif worker.is_idle and all_minerals_near_base:
                target_mineral = min(all_minerals_near_base, key=lambda mineral: mineral.distance_to(worker))
                actions.append(worker.gather(target_mineral))
            else:
                # there are no deficit mining places and worker is not idle
                # so dont move him
                pass

        await self.do_actions(actions)

上面的方法在sc2/sc2.BotAI文件中

第一次运行游戏

调用run_game方法设定合适的参数,这里AI选定星灵,敌方选择人类势力的简单人机

"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)

运行文件可以看到如下:

在这里插入图片描述

现阶段全部代码:
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty            #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑                        

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        

"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)

2.探机(工人)和折跃水晶塔(增加人口上限)

之前的代码只能分配工人,现在需要来写一个创造工人的方法
在星灵种族(Protoss)中主基地叫(Nexus)工人叫(Probe)
详见游戏内容参考资料 https://liquipedia.net/starcraft2/Protoss_Units_(Legacy_of_the_Void)

为了制造工人,根据关系图可知,要先访问Nexus,还要为了与工人交互,访问Probe,为了增加人口上限,访问Pylon。

from sc2.constants import UnitTypeId

为了制造工人,需要自定义一个方法build_workers()
        async def build_workers(self):
        #遍历全部的主基地(已经准备好的且没有队列的)
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            #如果能够支付的起工人
            if self.can_afford(UnitTypeId.PROBE):
                #让该目标制造一个工人
                await self.do(nexus.train(UnitTypeId.PROBE))
为了增加人口上限,需要自定义一个方法build_pylons()
    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)
现阶段全部代码
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty            #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()

    async def build_workers(self):
        #遍历全部的主基地(已经准备好的且没有队列的)
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            #如果能够支付的起工人
            if self.can_afford(UnitTypeId.PROBE):
                #让该目标制造一个工人
                await self.do(nexus.train(UnitTypeId.PROBE))

    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)

3.收集瓦斯和扩展基地

扩张基地

自定义方法expand()

    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()
建造瓦斯收集器

当需要进行进一步的发展时,需要从主基地附件的Vespene收集瓦斯,这种建筑物称为assimilator。
范围可做调整,15左右差不多。

    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(25, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))
现阶段所有代码
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty            #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()
        #扩张基地
        await self.expand()
        #建造瓦斯收集器
        await self.build_assimilator()


    async def build_workers(self):
        #遍历全部的主基地(已经准备好的且没有队列的)
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            #如果能够支付的起工人
            if self.can_afford(UnitTypeId.PROBE):
                #让该目标制造一个工人
                await self.do(nexus.train(UnitTypeId.PROBE))


    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()


    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(25, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))

"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = True)

在这里插入图片描述

4.建设军队

为了简单起见,建议构建单一的兵种,这里选择能够攻击地面和空中单位的兵种。
在这里插入图片描述
在这里插入图片描述
这里选择stalker为例,因为现在更容易使用。
可以看到它的制造前提为:
在这里插入图片描述
控制核心(Cybernet Core)是关于研究的,只需要造一个,如果需要加速出兵,则可以多造(Gateway)。
由于选择的是Protoss,使用需要在水晶塔的范围内才能建造建筑物

 async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random
            #如果已经存在GateWay
            if self.units(UnitTypeId.GATEWAY).ready.exists:
                #如果没有CyberneticsCore,而且能支付的起且没有正在建造中的,则建造CyberneticsCore
                if not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and \
                        not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            #如果不存在,且能够支付得起,而且没有正在建造中的,则建造
            else:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

详情见注释

建造军队

类似于建造工人

    async def build_army(self):
        #遍历所有已经建好的且没有队列的Gateway
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            #如果能支付得起且可用人口大于0,则训练Stalker
            if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                await self.do(gw.train(UnitTypeId.STALKER))
现阶段所有代码
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty            #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()
        #扩张基地
        await self.expand()
        #建造瓦斯收集器
        await self.build_assimilator()
        #建造能生产军队的建筑
        await self.build_army_buildings()
        #建造军队
        await self.build_army()


    async def build_workers(self):
        #遍历全部的主基地(已经准备好的且没有队列的)
        for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
            #如果能够支付的起工人
            if self.can_afford(UnitTypeId.PROBE):
                #让该目标制造一个工人
                await self.do(nexus.train(UnitTypeId.PROBE))


    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < 2 and self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()


    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(25, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))


    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random
            #如果已经存在GateWay
            if self.units(UnitTypeId.GATEWAY).ready.exists:
                #如果没有CyberneticsCore,而且能支付的起且没有正在建造中的,则建造CyberneticsCore
                if not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and \
                        not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            #如果不存在,且能够支付得起,而且没有正在建造中的,则建造
            else:
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)


    async def build_army(self):
        #遍历所有已经建好的且没有队列的Gateway
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            #如果能支付得起且可用人口大于0,则训练Stalker
            if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                await self.do(gw.train(UnitTypeId.STALKER))

"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)], realtime = False)

在这里插入图片描述

5.指挥军队

有了造兵的功能,现在可以指挥军队了

自定义一个简单的攻击方法
    async def attack_enemy(self):
        if self.units(UnitTypeId.STALKER).amount > 3:
            if len(self.known_enemy_units) > 0:
                for s in self.units(UnitTypeId.STALKER).idle:
                    await self.do(s.attack(random.choice(self.known_enemy_units)))

如果小兵的数量大于3且发现了敌人,则遍历在闲置状态的小兵去攻击一个随机的敌人。

扩展攻击方法

首先需要一个用来寻找敌方目标的方法
自定义方法find_target()

    def find_target(self):
        #如果发现了敌方单位
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        # 如果发现了敌方建筑
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        #什么都没发现直接去敌方出生点
        else:
            return self.enemy_start_locations[0]

改进攻击方法

    async def attack_enemy(self):
        #总兵数大于3,保守
        if self.units(UnitTypeId.STALKER).amount > 3:
            if len(self.known_enemy_units) > 0:
                for s in self.units(UnitTypeId.STALKER).idle:
                    await self.do(s.attack(random.choice(self.known_enemy_units)))
        #总兵数大于15,激进
        if self.units(UnitTypeId.STALKER).amount > 15:
            for s in self.units(UnitTypeId.STALKER).idle:
                await self.do(s.attack(self.find_target()))

6.击败困难人机

目前的问题是AI没有时间概念,而且没有合理的平衡工人和军队的人数
经过测试每分钟大约165次迭代

定义每分钟迭代数
#初始化
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165

在on_step中添加记录

    async def on_step(self, iteration):
        #记录已经迭代的次数
        self.iteration = iteration
        ......
改进建造GateWay的方法
    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random
            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
            #如果gateway的数量小于分钟数
            elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
控制工人数量
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165
        #定义最大工人数
        self.MAX_WORKERS = 50

如果要动态的控制工人的数量,则:
改进后建造工人的方法

    async def build_workers(self):
        #动态控制工人数量
        if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
            len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            #遍历全部的主基地(已经准备好的且没有队列的)
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                #如果能够支付的起工人
                if self.can_afford(UnitTypeId.PROBE):
                    #让该目标制造一个工人
                    await self.do(nexus.train(UnitTypeId.PROBE))
动态扩展基地

与其一次扩张3个基地,不如按照时间来扩张,这样更加合理

    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE ) and \
                self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()
空军

由于后期Stalker不强,不如建立一支空军(Void Ray)
要想建立(Void Ray)还需要一个建筑(Stargate)

再次改进(军队建筑)

    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random
            
            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
                    
            #如果gateway的数量小于分钟数/2
            elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)
            
            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

造兵

    async def build_army(self):
        #遍历所有已经建好的且没有队列的Gateway
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            if self.units(UnitTypeId.STALKER).amount <= self.units(UnitTypeId.VOIDRAY).amount:
                #如果能支付得起且可用人口大于0,则训练Stalker
                if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                    await self.do(gw.train(UnitTypeId.STALKER))
        #空军
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))
改进攻击方法
    async def attack_enemy(self):
        #[0:进攻, 1:防守]
        army_types = {UnitTypeId.STALKER : [15, 5], UnitTypeId.VOIDRAY : [8, 3]}
        for n in army_types:
            # 激进
            if self.units(n).amount > army_types[n][0]:
                for s in self.units(n).idle:
                    await self.do(s.attack(self.find_target()))
            # 保守
            if self.units(n).amount > army_types[n][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UnitTypeId.STALKER).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))
现阶段代码及总结
import sc2                                                       
from sc2 import run_game, maps, Race, Difficulty            #导入了运行游戏、地图、种族、难度
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #初始化
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165
        #定义最大工人数
        self.MAX_WORKERS = 50


    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #记录已经迭代的次数
        self.iteration = iteration
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()
        #扩张基地
        await self.expand()
        #建造瓦斯收集器
        await self.build_assimilator()
        #建造能生产军队的建筑
        await self.build_army_buildings()
        #建造军队
        await self.build_army()
        #军队作战
        await self.attack_enemy()


    async def build_workers(self):
        #动态控制工人数量
        if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
            len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            #遍历全部的主基地(已经准备好的且没有队列的)
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                #如果能够支付的起工人
                if self.can_afford(UnitTypeId.PROBE):
                    #让该目标制造一个工人
                    await self.do(nexus.train(UnitTypeId.PROBE))


    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE / 2) and \
                self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()


    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))


    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random

            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

            #如果gateway的数量小于分钟数/2
            elif len(self.units(UnitTypeId.GATEWAY)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE / 2):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)


    async def build_army(self):
        #遍历所有已经建好的且没有队列的Gateway
        for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            if self.units(UnitTypeId.STALKER).amount <= self.units(UnitTypeId.VOIDRAY).amount:
                #如果能支付得起且可用人口大于0,则训练Stalker
                if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                    await self.do(gw.train(UnitTypeId.STALKER))
        #空军
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))


    async def attack_enemy(self):
        #[0:进攻, 1:防守]
        army_types = {UnitTypeId.STALKER : [15, 5], UnitTypeId.VOIDRAY : [8, 3]}
        for n in army_types:
            # 激进
            if self.units(n).amount > army_types[n][0]:
                for s in self.units(n).idle:
                    await self.do(s.attack(self.find_target()))
            # 保守
            if self.units(n).amount > army_types[n][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UnitTypeId.STALKER).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))



    def find_target(self):
        #如果发现了敌方单位
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        # 如果发现了敌方建筑
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        #什么都没发现直接去敌方出生点
        else:
            return self.enemy_start_locations[0]

"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Hard)], realtime = False)

在这里插入图片描述
现在基本上能够战胜困难人机了
现阶段可以从GitHub上下载别人的AI来进行对战。

7.添加深度学习

尝试添加深度学习的进化算法。
使用遗传进化算法可以使获胜的成为训练数据(基因库),失败的则被遗忘(淘汰)。

更改进攻

只使用voidray
建筑

    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random

            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

            #如果gateway的数量小于1
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

造兵

    async def build_army(self):
        #空军
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))

攻击

    async def attack_enemy(self):
        #[0:进攻, 1:防守]
        army_types = {#UnitTypeId.STALKER : [15, 5],
         UnitTypeId.VOIDRAY : [8, 3]}
        for n in army_types:
            for s in self.units(n).idle:
                await self.do(s.attack(self.find_target()))

可以选择全部进攻或者不进攻。
在整个游戏中有许多的变量,我们要可视化此数据,并且传给神经网络,建议使用卷积神经网络。

准备工作

安装cv2,numpy,matplotlib

import cv2
import numpy as np

自定义一个方法,用来可视化数据

    """攻击之前执行此操作"""
    async def intel(self):
        #200 176 3  按行绘制RGB(200个矩阵)
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
        for nexus in self.units(UnitTypeId.NEXUS):
            n_pos = nexus.position
            cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), 10, (0,255,0), -1)
        #图像垂直翻转
        flipped = cv2.flip(game_data, 0)
        #调整图像大小,原图像,输出图像所需大小,比例因子
        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

        cv2.imshow("Intel", resized)
        cv2.waitKey(1)

8.将更多的数据进行可视化

绘制所有单位、建筑

首先,我们要可视化其余单元。创建一个draw_dict

#绘制己方所有单元
        draw_dict = {
                     UnitTypeId.NEXUS: [15, (0, 255, 0)],
                     UnitTypeId.PYLON: [3, (20, 235, 0)],
                     UnitTypeId.PROBE: [1, (55, 200, 0)],
                     UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                     UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                     UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                     UnitTypeId.STARGATE: [5, (255, 0, 0)],
                     UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
                    }
        for unit in draw_dict:
            for n in self.units(unit).ready:
                n_pos = n.position
                cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][1], -1)

        #绘制敌方的单元
        #建筑
        main_base_names = ["nexus", "commandcenter", "hatchery"]
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #非主基地
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #主基地
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
        #敌方单位
        for enemy_unit in self.known_enemy_units:
            if not enemy_unit.is_structure:
                worker_names = ["probe",
                                "scv",
                                "drone"]
                pos = enemy_unit.position
                #工人
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                #军队
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)
侦察敌方信息

组建一个侦查员(observer)还需要一个建筑(Robotics Facility)
在这里插入图片描述
在这里插入图片描述
更改方法

    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random

            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

            #如果gateway的数量小于1
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

            # 如果有了控制核心,则建造侦察兵基地
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于1
                if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
                        await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)

添加自定义方法(侦查员)

    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x, y)))
        return go_to
    #侦察兵
    async def scout(self):
        #有则行动
        if len(self.units(UnitTypeId.OBSERVER)) > 0:
            scout = self.units(UnitTypeId.OBSERVER)[0]
            #闲置的侦察兵
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                #侦察敌人出生地附近的位置
                move_to = self.random_location_variance(enemy_location)
                await self.do(scout.move(move_to))
        #没有则建造
        else:
            for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(UnitTypeId.OBSERVER))
现阶段代码
import sc2
from sc2 import run_game, maps, Race, Difficulty,position            #导入了运行游戏、地图、种族、难度、位置
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
import cv2
import numpy as np

"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #初始化
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165
        #定义最大工人数
        self.MAX_WORKERS = 50


    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #记录已经迭代的次数
        self.iteration = iteration
        #侦察兵
        await  self.scout()
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()
        #扩张基地
        await self.expand()
        #建造瓦斯收集器
        await self.build_assimilator()
        #建造能生产军队的建筑
        await self.build_army_buildings()
        #建造军队
        await self.build_army()
        #可视化
        await self.intel()
        #军队作战
        await self.attack_enemy()



    async def build_workers(self):
        #动态控制工人数量
        if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
            len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            #遍历全部的主基地(已经准备好的且没有队列的)
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                #如果能够支付的起工人
                if self.can_afford(UnitTypeId.PROBE):
                    #让该目标制造一个工人
                    await self.do(nexus.train(UnitTypeId.PROBE))


    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and \
                self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()


    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))


    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random

            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

            #如果gateway的数量小于1
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

            # 如果有了控制核心,则建造侦察兵基地
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于1
                if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
                        await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)


    #侦察兵
    async def scout(self):
        #有则行动
        if len(self.units(UnitTypeId.OBSERVER)) > 0:
            scout = self.units(UnitTypeId.OBSERVER)[0]
            #闲置的侦察兵
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                #侦察敌人出生地附近的位置
                move_to = self.random_location_variance(enemy_location)
                await self.do(scout.move(move_to))
        #没有则建造
        else:
            for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(UnitTypeId.OBSERVER))


    async def build_army(self):
        #空军
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))


    async def attack_enemy(self):
        #[0:进攻, 1:防守]
        army_types = {UnitTypeId.VOIDRAY: [8, 3]}
        for UNIT in army_types:
            if self.units(UNIT).amount > army_types[UNIT][0]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target()))

            elif self.units(UNIT).amount > army_types[UNIT][1]:
                if len(self.known_enemy_units) > 0:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))



    def find_target(self):
        #如果发现了敌方单位
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        # 如果发现了敌方建筑
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        #什么都没发现直接去敌方出生点
        else:
            return self.enemy_start_locations[0]


    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x, y)))
        return go_to


    """攻击之前执行此操作"""
    async def intel(self):
        #200 176 3  按行绘制RGB(200个矩阵)
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        #绘制己方所有单元
        draw_dict = {
                     UnitTypeId.NEXUS: [15, (0, 255, 0)],
                     UnitTypeId.PYLON: [3, (20, 235, 0)],
                     UnitTypeId.PROBE: [1, (55, 200, 0)],
                     UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                     UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                     UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                     UnitTypeId.STARGATE: [5, (255, 0, 0)],
                     UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
                     UnitTypeId.OBSERVER: [1, (255, 255, 255)],
                     UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
                    }
        for unit in draw_dict:
            for n in self.units(unit).ready:
                n_pos = n.position
                cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][1], -1)

        #绘制敌方的单元
        #建筑
        main_base_names = ["nexus", "commandcenter", "hatchery"]
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #非主基地
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #主基地
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
        #敌方单位
        for enemy_unit in self.known_enemy_units:
            if not enemy_unit.is_structure:
                worker_names = ["probe",
                                "scv",
                                "drone"]
                pos = enemy_unit.position
                #工人
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                #军队
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

        #图像垂直翻转
        flipped = cv2.flip(game_data, 0)
        #调整图像大小,原图像,输出图像所需大小,比例因子
        resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

        cv2.imshow("Intel", resized)
        cv2.waitKey(1)




"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"), [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Hard)], realtime = True)

在这里插入图片描述

9.建立训练数据

加入其他数据的可视化

绘制数据线

#其他数据的可视化
        line_max = 50
        #矿物数量/1500
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0
        #瓦斯气的数量/1500
        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0
        #可用人口比例
        population_ratio = self.supply_left / self.supply_cap
        if population_ratio > 1.0:
            population_ratio = 1.0
        #人口上限数/200
        plausible_supply = self.supply_cap / 200.0
        #voidray占全部人口的比例
        military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
        if military_weight > 1.0:
            military_weight = 1.0
        #绘制数据线line(图片, 起点, 终点, RGB, 粗细)
        # voidray占全部人口的比例
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口上限数/200
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # 可用人口比例
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 瓦斯气的数量/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 矿物数量/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)
变更攻击方法

首先添加两个变量

class SentBot(sc2.BotAI):
    #初始化
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165
        #定义最大工人数
        self.MAX_WORKERS = 50
        #记录上一次迭代数
        self.do_something_after = 0
        #训练数据
        self.train_data = []

将攻击方法改为随机决策

   async def attack_enemy(self):
        #如果voidray有闲置的
        if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
            #随机一种决策
            choice = random.randrange(0, 4)
            #定义目标
            target = False
            #如果迭代数大于上一次的迭代数
            if self.iteration > self.do_something_after:
                #不攻击,等待20-165个迭代
                if choice == 0:
                    wait = random.randrange(20, 165)
                    self.do_something_after = self.iteration + wait
                #攻击离家最近的敌方单位
                elif choice == 1:
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
                #攻击敌方建筑物
                elif choice == 2:
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)
                #攻击敌方出生地
                elif choice == 3:
                    target = self.enemy_start_locations[0]
                #如果目标不为空,则让所有闲置的voidray做该决策
                if target:
                    for vr in self.units(UnitTypeId.VOIDRAY).idle:
                        await self.do(vr.attack(target))
                #每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
                y = np.zeros(4)
                y[choice] = 1
                print(y)
                self.train_data.append([y, self.flipped])

设置是否要可视化,添加

#类外添加
HEADLESS = False
...
		#图像垂直翻转
        self.flipped = cv2.flip(game_data, 0)
        #是否可视化
        if not HEADLESS:
            # 调整图像大小,原图像,输出图像所需大小,比例因子
            resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
            cv2.imshow("Intel", resized)
            cv2.waitKey(1)

只需要记录胜利的数据,因此添加on_end方法

    #结束
    def on_end(self, game_result: Result):
        print("!!!!!!game over!!!!!!")
        print(game_result)
        if game_result == Result.Victory:
            np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
现阶段全部代码
import sc2
from sc2 import run_game, maps, Race, Difficulty, position, Result            #导入了运行游戏、地图、种族、难度、位置、结果
from sc2.player import Bot, Computer      #Bot是将要编写的AI,Computer是对战的电脑
from sc2.constants import UnitTypeId
import random
import cv2
import numpy as np
import time


HEADLESS = False



"""
创建机器人类,继承自sc2.BotAI
"""
class SentBot(sc2.BotAI):
    #初始化
    def __init__(self):
        #每分钟迭代数
        self.ITERATIONS_PER_MINUTE = 165
        #定义最大工人数
        self.MAX_WORKERS = 50
        #记录上一次迭代数
        self.do_something_after = 0
        #训练数据
        self.train_data = []

    #结束
    def on_end(self, game_result: Result):
        if game_result == Result.Victory:
            np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
        print("!!!!!!game over!!!!!!")
        print(game_result)


    #用async异步就不需要等待每一个完成再做其他事
    async def on_step(self, iteration):
        #记录已经迭代的次数
        self.iteration = iteration
        #侦察兵
        await  self.scout()
        #每一步做什么
        await self.distribute_workers()         #父类中的方法
        #建造工人
        await self.build_workers()
        #建造水晶塔
        await self.build_pylons()
        #扩张基地
        await self.expand()
        #建造瓦斯收集器
        await self.build_assimilator()
        #建造能生产军队的建筑
        await self.build_army_buildings()
        #建造军队
        await self.build_army()
        #可视化
        await self.intel()
        #军队作战
        await self.attack_enemy()



    async def build_workers(self):
        #动态控制工人数量
        if len(self.units(UnitTypeId.PROBE)) < (len(self.units(UnitTypeId.NEXUS)) * 16) and \
            len(self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
            #遍历全部的主基地(已经准备好的且没有队列的)
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                #如果能够支付的起工人
                if self.can_afford(UnitTypeId.PROBE):
                    #让该目标制造一个工人
                    await self.do(nexus.train(UnitTypeId.PROBE))


    async def build_pylons(self):
        #如果人口上限 - 当前人口 < 5 并且当前没有正在建造的水晶塔
        if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
            #找到已经建好的主基地
            nexuses = self.units(UnitTypeId.NEXUS).ready
            #如果主基地存在
            if nexuses.exists:
                #如果能够支付的起水晶塔
                if self.can_afford(UnitTypeId.PYLON):
                    #在主基地附近建造水晶塔
                    await self.build(UnitTypeId.PYLON, near=nexuses.first)


    async def expand(self):
        #如果基地总数小于n且能够支付的起基地建造的费用
        if self.units(UnitTypeId.NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and \
                self.can_afford(UnitTypeId.NEXUS):
            #父类扩张基地方法
            await self.expand_now()


    async def build_assimilator(self):
        #遍历所有已经建好的主基地
        for nexus in self.units(UnitTypeId.NEXUS).ready:
            #定义所有离该主基地25个单元格的瓦斯间歇泉
            vespenes = self.state.vespene_geyser.closer_than(15.0, nexus)
            for vespene in vespenes:
                #如果支付不起则退出
                if not self.can_afford(UnitTypeId.ASSIMILATOR):
                    break
                #否则就在瓦斯泉的附近选一个工人
                worker = self.select_build_worker(vespene.position)
                #如果没有工人则退出
                if not worker:
                    break
                #如果瓦斯间歇泉上面没有瓦斯收集器就建一个瓦斯收集器
                if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vespene).exists:
                    await self.do(worker.build(UnitTypeId.ASSIMILATOR, vespene))


    async def build_army_buildings(self):
        #首先需要找到水晶塔
        if self.units(UnitTypeId.PYLON).ready.exists:
            #如果有水晶塔,则选择一个随机的水晶塔
            pylon = self.units(UnitTypeId.PYLON).ready.random

            #如果已经存在GateWay且没有CyberneticsCore
            if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                #如果能支付的起且没有正在建造中的,则建造CyberneticsCore
                if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                    await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)

            #如果gateway的数量小于1
            elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                #且能够支付得起,而且没有正在建造中的,则建造
                if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                    await self.build(UnitTypeId.GATEWAY, near=pylon)

            #如果有了控制核心,则建立Stargate
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于分钟数/2
                if len(self.units(UnitTypeId.STARGATE)) < (self.iteration / self.ITERATIONS_PER_MINUTE):
                    # 且能够支付得起,而且没有正在建造中的,则建造
                    if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                        await self.build(UnitTypeId.STARGATE, near=pylon)

            # 如果有了控制核心,则建造侦察兵基地
            if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                #数量小于1
                if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(UnitTypeId.ROBOTICSFACILITY):
                        await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)


    #侦察兵
    async def scout(self):
        #有则行动
        if len(self.units(UnitTypeId.OBSERVER)) > 0:
            scout = self.units(UnitTypeId.OBSERVER)[0]
            #闲置的侦察兵
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                #侦察敌人出生地附近的位置
                move_to = self.random_location_variance(enemy_location)
                await self.do(scout.move(move_to))
        #没有则建造
        else:
            for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(UnitTypeId.OBSERVER))


    async def build_army(self):
        #空军
        for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
            if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(UnitTypeId.VOIDRAY))


    async def attack_enemy(self):
        #如果voidray有闲置的
        if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
            #随机一种决策
            choice = random.randrange(0, 4)
            #定义目标
            target = False
            #如果迭代数大于上一次的迭代数
            if self.iteration > self.do_something_after:
                #不攻击,等待20-165个迭代
                if choice == 0:
                    wait = random.randrange(20, 165)
                    self.do_something_after = self.iteration + wait
                #攻击离家最近的敌方单位
                elif choice == 1:
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
                #攻击敌方建筑物
                elif choice == 2:
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)
                #攻击敌方出生地
                elif choice == 3:
                    target = self.enemy_start_locations[0]
                #如果目标不为空,则让所有闲置的voidray做该决策
                if target:
                    for vr in self.units(UnitTypeId.VOIDRAY).idle:
                        await self.do(vr.attack(target))
                #每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
                y = np.zeros(4)
                y[choice] = 1
                print(y)
                self.train_data.append([y, self.flipped])




    def find_target(self):
        #如果发现了敌方单位
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        # 如果发现了敌方建筑
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        #什么都没发现直接去敌方出生点
        else:
            return self.enemy_start_locations[0]


    #目标附近的随机位置
    def random_location_variance(self, enemy_start_location):
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]

        if x < 0:
            x = 0
        if y < 0:
            y = 0
        if x > self.game_info.map_size[0]:
            x = self.game_info.map_size[0]
        if y > self.game_info.map_size[1]:
            y = self.game_info.map_size[1]

        go_to = position.Point2(position.Pointlike((x, y)))
        return go_to


    """攻击之前执行此操作"""
    async def intel(self):
        #200 176 3  按行绘制RGB(200个矩阵)
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        #绘制己方所有单元
        draw_dict = {
                     UnitTypeId.NEXUS: [15, (0, 255, 0)],
                     UnitTypeId.PYLON: [3, (20, 235, 0)],
                     UnitTypeId.PROBE: [1, (55, 200, 0)],
                     UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                     UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                     UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                     UnitTypeId.STARGATE: [5, (255, 0, 0)],
                     UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
                     UnitTypeId.OBSERVER: [1, (255, 255, 255)],
                     UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
                    }
        for unit in draw_dict:
            for n in self.units(unit).ready:
                n_pos = n.position
                cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][1], -1)

        #绘制敌方的单元
        #建筑
        main_base_names = ["nexus", "commandcenter", "hatchery"]
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #非主基地
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            #主基地
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
        #敌方单位
        for enemy_unit in self.known_enemy_units:
            if not enemy_unit.is_structure:
                worker_names = ["probe",
                                "scv",
                                "drone"]
                pos = enemy_unit.position
                #工人
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                #军队
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

        #其他数据的可视化
        line_max = 50
        #矿物数量/1500
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0
        #瓦斯气的数量/1500
        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0
        #可用人口比例
        population_ratio = self.supply_left / self.supply_cap
        if population_ratio > 1.0:
            population_ratio = 1.0
        #人口上限数/200
        plausible_supply = self.supply_cap / 200.0
        #voidray占全部人口的比例
        military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
        if military_weight > 1.0:
            military_weight = 1.0
        #绘制数据线line(图片, 起点, 终点, RGB, 粗细)
        # voidray占全部人口的比例
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口上限数/200
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # 可用人口比例
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 瓦斯气的数量/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 矿物数量/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

        #图像垂直翻转
        self.flipped = cv2.flip(game_data, 0)
        #是否可视化
        if not HEADLESS:
            # 调整图像大小,原图像,输出图像所需大小,比例因子
            resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
            cv2.imshow("Intel", resized)
            cv2.waitKey(1)




"""
运行游戏,选择地图,自定义选手(AI vs computer) or (AI vs AI), 是否实时运行
"""
run_game(maps.get("AbyssalReefLE"),
         [Bot(Race.Protoss, SentBot()), Computer(Race.Terran, Difficulty.Easy)],
         realtime=False)

在这里插入图片描述
如果游戏胜利,则可以看到记录了一条数据
在这里插入图片描述
也可以查看其内容:
在这里插入图片描述

代码重构

目的:为了更好收集训练数据,对现有代码进行解耦并引入多进程加快游戏数据的收集效率

main.py

import os
import random
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from mybot.protoss.normal import MyBotAI
from examples.zerg.zerg_rush import ZergRushBot
from multiprocessing.pool import Pool

# 游戏地图
map_name = "AbyssalReefLE"
# 模式开关
target = 0
# 进程数量
process_core = 8
# 任务队列数
task_num = 1024

# 种族数组
race_arr = [Race.Protoss, Race.Zerg, Race.Terran]

# 比赛难度
hard_arr = [
    # Difficulty.Easy,
    # Difficulty.Medium,
    # Difficulty.MediumHard,
    Difficulty.Hard,
    Difficulty.Harder
]


def stand(real=False):
    """ 标准模式 """
    run_game(maps.get(map_name), [
        Bot(Race.Protoss, MyBotAI(ENABLE_VIEW=True)),
        Computer(random.choice(race_arr), random.choice(hard_arr))
    ], realtime=real)


def run(index):
    """ 多进程模式 收集数据"""
    print(f"子进程{index}的id为{os.getpid()}")
    run_game(maps.get(map_name), [
        Bot(Race.Protoss, MyBotAI()),
        Computer(random.choice(race_arr), random.choice(hard_arr))
    ], realtime=False)


if __name__ == '__main__':
    if target == 0:
        stand()
    else:
        p = Pool(process_core)
        for i in range(task_num):
            p.apply_async(run, args=(i, ))
        p.close()
        p.join()
        print("process over")

AI

import random
import sc2
import cv2
import numpy as np
import time
import tensorflow as tf
from tensorflow.keras import models


class MyBotAI(sc2.BotAI):
    """
    自定义AI
    """

    def __init__(self, ENABLE_VIEW=False, USE_MODEL=False):
        super().__init__()
        # 最大工人数
        self.MAX_WORKERS = 64
        # 下一次进行决策的时间
        self.next_do_something_time = 0
        # 收集训练数据
        self.train_data = []
        # 是否打开cv视图
        self.ENABLE_VIEW = ENABLE_VIEW
        # 是否使用网络模型
        self.USE_MODEL = USE_MODEL

    # 结束
    def on_end(self, game_result: sc2.Result):
        print("!!!!!!game over!!!!!!")
        print(game_result)
        if game_result == sc2.Result.Victory:
            np.save(r"A:\resources\train_data\sc2\protoss\v1\train\{}.npy".format(str(int(time.time()))),
                    np.array(self.train_data))

    async def on_step(self, iteration: int):
        """ 在每个游戏步骤上运行(在实时模式下循环)。 """
        # 将工人分配到所有基地。
        await self.distribute_workers()
        # 生产工人
        await self.build_works()
        # 建造水晶塔
        await self.build_pylons()
        # 建造瓦斯收集
        await self.build_assimilator()
        # 扩建基地
        await self.expand()
        # 建造兵营
        await self.build_barracks()
        # 建造军队
        await self.build_army()
        # 部队攻击
        await self.attack_enemy()
        # 绘制图像
        await self.view()
        # 侦察
        await self.lookFor()

    async def view(self):
        """ 根据open cv 绘制游戏的实时图像 """
        # 创建游戏图像矩阵
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        # 绘制己方单位
        draw_dict = {
            sc2.UnitTypeId.NEXUS: [15, (0, 255, 0)],
            sc2.UnitTypeId.PYLON: [3, (20, 235, 0)],
            sc2.UnitTypeId.PROBE: [1, (55, 200, 0)],
            sc2.UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
            sc2.UnitTypeId.GATEWAY: [3, (200, 100, 0)],
            sc2.UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
            sc2.UnitTypeId.STARGATE: [5, (255, 0, 0)],
            sc2.UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
            sc2.UnitTypeId.OBSERVER: [1, (255, 255, 255)],
            sc2.UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
        }

        for unit in draw_dict:
            for n in self.units(unit).ready:
                n_pos = n.position
                cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][1], -1)

        # 绘制敌方的单元
        # 建筑
        main_base_names = ["nexus", "commandcenter", "hatchery"]
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            # 非主基地
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            # 主基地
            if enemy_building.name.lower() in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
        # 敌方单位
        for enemy_unit in self.known_enemy_units:
            if not enemy_unit.is_structure:
                worker_names = ["probe",
                                "scv",
                                "drone"]
                pos = enemy_unit.position
                # 工人
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                # 军队
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

        # 其他数据的可视化
        line_max = 50
        # 矿物数量/1500
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0
        # 瓦斯气的数量/1500
        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0
        # 可用人口比例
        population_ratio = self.supply_left / (self.supply_cap + 1e-4)
        if population_ratio > 1.0:
            population_ratio = 1.0
        # 人口上限数/200
        plausible_supply = self.supply_cap / 200.0
        # voidray占全部人口的比例
        military_weight = len(self.units(sc2.UnitTypeId.VOIDRAY)) / ((self.supply_cap - self.supply_left) + 1e-4)
        if military_weight > 1.0:
            military_weight = 1.0
        # 绘制数据线line(图片, 起点, 终点, RGB, 粗细)
        # voidray占全部人口的比例
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口上限数/200
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # 可用人口比例
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 瓦斯气的数量/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 矿物数量/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

        # 处理,翻转和缩放
        self.temp_data = cv2.flip(game_data, 0)
        # 是否可视化
        if self.ENABLE_VIEW:
            # 调整图像大小,原图像,输出图像所需大小,比例因子
            cv2.resize(self.temp_data, dsize=None, fx=2, fy=2)
            # 展示
            cv2.imshow("sc2 opencv view", self.temp_data)
            # 刷新
            cv2.waitKey(1)

    async def build_works(self):
        """ 建造工人 probe 数量不超过64,前期根据基地的数量动态生成"""
        if len(self.units(sc2.UnitTypeId.NEXUS) * 16) > len(self.units(sc2.UnitTypeId.PROBE)):
            if len(self.units(sc2.UnitTypeId.PROBE)) < self.MAX_WORKERS:
                # 遍历完成的,没有队列的主基地 nexus
                for nexus in self.units(sc2.UnitTypeId.NEXUS).ready.idle:
                    # 判断能否建造
                    if self.can_afford(sc2.UnitTypeId.PROBE):
                        #  建造农民 train()命令单位训练另一个“单位”。
                        await self.do(nexus.train(sc2.UnitTypeId.PROBE))

    async def build_pylons(self):
        """ 建造水晶塔 """
        # 如果可用人口小于5并且没有正在建造的水晶塔就开始建造
        if self.supply_left < 5 and not self.already_pending(sc2.UnitTypeId.PYLON):
            # 先找到list<基地>
            nexuses = self.units(sc2.UnitTypeId.NEXUS).ready
            # 如果有基地并且可以造
            if nexuses.exists and self.can_afford(sc2.UnitTypeId.PYLON):
                await self.build(sc2.UnitTypeId.PYLON, near=nexuses.random)

    async def build_assimilator(self):
        """ 建造瓦斯收集器 """
        # 遍历所有可使用的基地
        for nexus in self.units(sc2.UnitTypeId.NEXUS).ready:
            # 基地附近的list<瓦斯>
            vespenes = self.state.vespene_geyser.closer_than(8, nexus)
            # 遍历所有瓦斯泉
            for vespene in vespenes:
                # 判断有余钱
                if self.can_afford(sc2.UnitTypeId.ASSIMILATOR):
                    worker = self.select_build_worker(vespene.position)
                    # 判断附件有工人并且瓦斯泉上没有建筑
                    if worker and not self.units(sc2.UnitTypeId.ASSIMILATOR).closer_than(1, vespene).exists:
                        await self.do(worker.build(sc2.UnitTypeId.ASSIMILATOR, vespene))

    async def expand(self):
        """ 根据当前基地总数扩张 对数函数"""
        if self.units(sc2.UnitTypeId.NEXUS).amount < self.game_time_of_minute() / 1.75 and \
                self.can_afford(sc2.UnitTypeId.NEXUS):
            await self.expand_now()

    async def build_barracks(self):
        """ 建造网关Gateway和控制核心Cybernetics Core """
        # 首先找到可用的水晶塔并且随机选择一个
        if self.units(sc2.UnitTypeId.PYLON).ready.exists:
            pylon = self.units(sc2.UnitTypeId.PYLON).ready.random
            # 存在网关就造1个核心,否则就造网关
            if self.units(sc2.UnitTypeId.GATEWAY).ready.exists and not self.units(sc2.UnitTypeId.CYBERNETICSCORE):
                # 可以支付的起并且没有正在建造的
                if not self.already_pending(sc2.UnitTypeId.CYBERNETICSCORE) \
                        and self.can_afford(sc2.UnitTypeId.CYBERNETICSCORE):
                    await self.build(sc2.UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 建造网关
            # elif len(self.units(sc2.UnitTypeId.GATEWAY)) < self.game_time_of_minute() / 2 + 1:
            elif len(self.units(sc2.UnitTypeId.GATEWAY)) < 1:
                # 可以支付的起并且没有正在建造的
                if self.can_afford(sc2.UnitTypeId.GATEWAY) and not self.already_pending(sc2.UnitTypeId.GATEWAY):
                    await self.build(sc2.UnitTypeId.GATEWAY, near=pylon)

            # 建造星门
            if self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(sc2.UnitTypeId.GATEWAY)) < self.game_time_of_minute() / 2 + 1:
                    if self.can_afford(sc2.UnitTypeId.STARGATE) and not self.already_pending(sc2.UnitTypeId.STARGATE):
                        await self.build(sc2.UnitTypeId.STARGATE, near=pylon)

            # 建造侦察兵营地
            if self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(sc2.UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(sc2.UnitTypeId.ROBOTICSFACILITY) and \
                            not self.already_pending(sc2.UnitTypeId.ROBOTICSFACILITY):
                        await self.build(sc2.UnitTypeId.ROBOTICSFACILITY, near=pylon)

    async def build_army(self):
        """ 建造 Stalker """
        # 遍历所有已经建好的且没有队列的Gateway
        # for gw in self.units(sc2.UnitTypeId.GATEWAY).ready.idle:
        #     # 如果能支付得起且可用人口大于2,则训练Stalker
        #     if self.units(sc2.UnitTypeId.STALKER).amount <= self.units(sc2.UnitTypeId.VOIDRAY).amount:
        #         if self.can_afford(sc2.UnitTypeId.STALKER) and self.supply_left > 2:
        #             await self.do(gw.train(sc2.UnitTypeId.STALKER))
        # 遍历所有的星门
        for sg in self.units(sc2.UnitTypeId.STARGATE).ready.idle:
            if self.can_afford(sc2.UnitTypeId.VOIDRAY) and self.supply_left > 4:
                await self.do(sg.train(sc2.UnitTypeId.VOIDRAY))

    async def attack_enemy(self):
        """ 随机攻击的方法 """
        # 如果voidray有闲置的
        if len(self.units(sc2.UnitTypeId.VOIDRAY).idle) > 0:

            if self.USE_MODEL:
                # 根据图像预测结果
                choice_dict = {0: "等待", 1: "攻击敌方单位", 2: "攻击对方建筑", 3: "攻击对方出生地"}
                prediction = self.model.predict(self.temp_data.reshape((-1, 176, 200, 3)))
                choice = np.argmax(prediction)
                print("机器学习预测的当前最佳策略为:", choice_dict[choice])
            else:
                # 随机一种决策[0, 1, 2, 3]
                choice = random.randrange(0, 4)
            # 定义目标
            target = False
            # 如果当前时间大于定义的时间
            if self.game_time_of_minute() > self.next_do_something_time:
                # 不攻击,等待
                if choice == 0:
                    wait = random.randrange(6, 60) / 60
                    self.next_do_something_time = self.game_time_of_minute() + wait
                # 攻击离家最近的敌方单位
                elif choice == 1:
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(sc2.UnitTypeId.NEXUS)))
                # 攻击敌方建筑物
                elif choice == 2:
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)
                # 攻击敌方出生地
                elif choice == 3:
                    target = self.enemy_start_locations[0]
                # 如果目标不为空,则让所有闲置的voidray做该决策
                if target:
                    for vr in self.units(sc2.UnitTypeId.VOIDRAY).idle:
                        await self.do(vr.attack(target))
                # 每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
                y = np.zeros(4)
                y[choice] = 1
                self.train_data.append([y, self.temp_data])

    def find_enemy_target(self):
        """ 寻找敌方目标 敌方单位 -》 敌方建筑物 -》 敌方出生点 """
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        else:
            return self.enemy_start_locations[0]

    def game_time_of_minute(self):
        """ 游戏分钟数 """
        return self.time / 60.0 + 1e-8

    def research_science(self):
        """ 研究科技 """
        for rs in self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.idle:
            self.do(rs.research(sc2.AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1))

    async def lookFor(self):
        """ 侦察敌方情况 """
        # 有则行动
        if len(self.units(sc2.UnitTypeId.OBSERVER)) > 0:
            scout = self.units(sc2.UnitTypeId.OBSERVER)[0]
            # 闲置的侦察兵
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                # 侦察敌人出生地附近的位置
                move_to = self.random_location_variance(enemy_location)
                await self.do(scout.move(move_to))
        # 没有则建造
        else:
            for rf in self.units(sc2.UnitTypeId.ROBOTICSFACILITY).ready.idle:
                if self.can_afford(sc2.UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(sc2.UnitTypeId.OBSERVER))

    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 = sc2.position.Point2(sc2.position.Pointlike((x, y)))
        return go_to

开始搜集训练数据

一次开6个
在这里插入图片描述
在这里插入图片描述
一上午就能收集200个

可以看到会有一处报错
在这里插入图片描述
需要改官方源码,加一个判断条件
在这里插入图片描述

10.建立神经网络模型

安装tensorflow-gpu、keras
建议在conda里训练 codna install tensorflow-gpu==2.6.0

import tensorflow as tf
from tensorflow.keras import layers, activations, optimizers, losses, metrics


model = tf.keras.Sequential()

model.add(layers.Conv2D(32, (3, 3), input_shape=(176, 200, 3), padding="same", activation=activations.relu))
model.add(layers.Conv2D(32, (3, 3),  activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.2))
model.add(layers.Conv2D(64, (3, 3), padding="same", activation=activations.relu))
model.add(layers.Conv2D(64, (3, 3),  activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.5))
model.add(layers.Conv2D(128, (3, 3), padding="same", activation=activations.relu))
model.add(layers.Conv2D(128, (3, 3),  activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.5))
model.add(layers.Conv2D(256, (3, 3), padding="same", activation=activations.relu))
model.add(layers.Conv2D(256, (3, 3),  activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Dropout(0.2))

model.add(layers.Flatten())
model.add(layers.Dense(512,activation=activations.relu))
model.add(layers.Dropout(0.2))
# model.add(layers.Dense(1024,activation=activations.relu))
# model.add(layers.Dropout(0.1))
model.add(layers.Dense(4, activation=activations.softmax))

11.训练神经网络模型

不支持gpu版本的也可以用cpu

# 降低学习率
lr = 1e-4
opt = optimizers.Adam(learning_rate=lr, decay=1e-6)

# 加入早停技术
early_stopping = tf.keras.callbacks.EarlyStopping(
    monitor='val_loss',
    min_delta=1e-3,
    patience=3,
    verbose=1,
    restore_best_weights=True)


model.compile(loss=losses.categorical_crossentropy,
              optimizer=opt,
              metrics=[metrics.categorical_accuracy])

print(model.summary())

"""
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_20 (Conv2D)           (None, 176, 200, 32)      896       
_________________________________________________________________
conv2d_21 (Conv2D)           (None, 174, 198, 32)      9248      
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 87, 99, 32)        0         
_________________________________________________________________
dropout_2 (Dropout)          (None, 87, 99, 32)        0         
_________________________________________________________________
conv2d_22 (Conv2D)           (None, 87, 99, 64)        18496     
_________________________________________________________________
conv2d_23 (Conv2D)           (None, 85, 97, 64)        36928     
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 42, 48, 64)        0         
_________________________________________________________________
dropout_3 (Dropout)          (None, 42, 48, 64)        0         
_________________________________________________________________
conv2d_24 (Conv2D)           (None, 42, 48, 128)       73856     
_________________________________________________________________
conv2d_25 (Conv2D)           (None, 40, 46, 128)       147584    
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 20, 23, 128)       0         
_________________________________________________________________
dropout_4 (Dropout)          (None, 20, 23, 128)       0         
_________________________________________________________________
conv2d_26 (Conv2D)           (None, 20, 23, 256)       295168    
_________________________________________________________________
conv2d_27 (Conv2D)           (None, 18, 21, 256)       590080    
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 9, 10, 256)        0         
_________________________________________________________________
dropout_5 (Dropout)          (None, 9, 10, 256)        0         
_________________________________________________________________
flatten_1 (Flatten)          (None, 23040)             0         
_________________________________________________________________
dense_3 (Dense)              (None, 512)               11796992  
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_4 (Dense)              (None, 4)                 2052      
=================================================================
Total params: 12,971,300
Trainable params: 12,971,300
Non-trainable params: 0
_________________________________________________________________
None
"""

train_data_dir = r"A:\resources\train_data\sc2\protoss\v1\train"
# 迭代次数
epochs = 128
# 创建TensorBoard记录训练过程
logDir = r"logs\stage1\{}".format(time.time())
call_back_tb = TensorBoard(log_dir=logDir)
file_writer = tf.summary.create_file_writer(logDir) 

all_files = os.listdir(train_data_dir)
random.shuffle(all_files)
# 决策队列
no_attacks = []
attack_closest_enemy = []
attack_closest_structure = []
attack_enemy_start = []
# 遍历文件并根据target将列表添加到决策队列
for file in all_files:
    full_path = os.path.join(train_data_dir, file)
    data = np.load(full_path, allow_pickle=True)
    data = list(data)
    for d in data:
        choice = np.argmax(d[0])
        if choice == 0:
            no_attacks.append(d)
        elif choice == 1:
            attack_closest_enemy.append(d)
        elif choice == 2:
            attack_closest_structure.append(d)
        elif choice == 3:
            attack_enemy_start.append(d)
# 每个决策队列的长度,计算出最小的长度
lengthes = check_data()
lowest_data = min(lengthes)
""" 要保证所有的可能出现的频次一致 """
random.shuffle(no_attacks)
random.shuffle(attack_closest_enemy)
random.shuffle(attack_closest_structure)
random.shuffle(attack_enemy_start)

no_attacks = no_attacks[:lowest_data]
attack_closest_enemy = attack_closest_enemy[:lowest_data]
attack_closest_structure = attack_closest_structure[:lowest_data]
attack_enemy_start = attack_enemy_start[:lowest_data]
# 验证长度是否一致
check_data()

train_data = no_attacks + attack_closest_enemy + attack_closest_structure + attack_enemy_start
random.shuffle(train_data)
# 测试集大小
test_size = 50
# 训练批次大小
batch_size = 16

# 分割出训练集和测试集
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:]])

with file_writer.as_default():
    tf.summary.image("train_data", x_test, step=1)

x_train = x_train/ 255.0
x_test = x_test / 255.0
    
print(x_train[1])
print("形状:", x_train[0].shape)

"""
no_attacks列表的长度是1671
attack_closest_enemy列表的长度是1959
attack_closest_structure列表的长度是1750
attack_enemy_start列表的长度是1771
total_data length now is :  7151
no_attacks列表的长度是1671
attack_closest_enemy列表的长度是1671
attack_closest_structure列表的长度是1671
attack_enemy_start列表的长度是1671
total_data length now is :  6684
"""

""" 开始训练卷积神经网络 """
print("---------开始训练卷积神经网络----------")
model.fit(x_train, y_train,
          epochs=epochs,
          batch_size=batch_size,
          validation_data=(x_test, y_test),
          shuffle=True, callbacks=[call_back_tb, early_stopping])
test_loss, test_accuracy = model.evaluate(x_test, y_test)
print('Test accuracy:', test_accuracy)
# 保存每一个阶段的模型
model.save("{}_CNN_{}_epochs_{}_LR_{}_valAcc.h5".format("protoss", epochs, lr, round(test_accuracy, 4)))
# tf.keras.models.save_model(filepath="{}_CNN_{}_epochs_{}_LR_{}_valAcc".format("protoss", epochs, lr, round(test_accuracy, 4)))

"""
---------开始训练卷积神经网络----------
Epoch 1/128
415/415 [==============================] - 11s 25ms/step - loss: 1.3877 - categorical_accuracy: 0.2538 - val_loss: 1.3874 - val_categorical_accuracy: 0.2400
Epoch 2/128
415/415 [==============================] - 7s 18ms/step - loss: 1.3855 - categorical_accuracy: 0.2639 - val_loss: 1.3815 - val_categorical_accuracy: 0.3200
Epoch 3/128
415/415 [==============================] - 8s 18ms/step - loss: 1.3847 - categorical_accuracy: 0.2712 - val_loss: 1.3867 - val_categorical_accuracy: 0.2200
Epoch 4/128
415/415 [==============================] - 8s 18ms/step - loss: 1.3844 - categorical_accuracy: 0.2704 - val_loss: 1.3848 - val_categorical_accuracy: 0.2800
Epoch 5/128
415/415 [==============================] - 7s 18ms/step - loss: 1.3829 - categorical_accuracy: 0.2763 - val_loss: 1.3862 - val_categorical_accuracy: 0.2200
Restoring model weights from the end of the best epoch.
Epoch 00005: early stopping
2/2 [==============================] - 0s 178ms/step - loss: 1.3815 - categorical_accuracy: 0.3200
Test accuracy: 0.3199999928474426
"""

可以看出验证精度只有32%,因为数据量太小很快过拟合

可以尝试更换网络查看效果

input_shape = (176, 200, 3)
num_classes = 4

def inception_module(prev_layer, filters):
    f1, f3, f5 = filters
    # 1x1 Convolution
    conv1 = layers.Conv2D(f1, (1,1), padding='same', activation='relu')(prev_layer)
    # 3x3 Convolution
    conv3 = layers.Conv2D(f3, (3,3), padding='same', activation='relu')(prev_layer)
    # 5x5 Convolution
    conv5 = layers.Conv2D(f5, (5,5), padding='same', activation='relu')(prev_layer)
    # Max pooling
    pool = layers.MaxPooling2D((3,3), strides=(1,1), padding='same')(prev_layer)
    pool_conv = layers.Conv2D(f1, (1,1), padding='same', activation='relu')(pool)
    # Concatenate all of the filters
    output = layers.concatenate([conv1, conv3, conv5, pool_conv], axis=-1)
    return output

input_layer = layers.Input(shape=input_shape)

# Initial convolutions and pooling
x = layers.Conv2D(64, (7,7), strides=(2,2), padding='same', activation='relu')(input_layer)
x = layers.MaxPooling2D((3,3), strides=(2,2), padding='same')(x)

# First Inception module
x = inception_module(x, [64, 96, 128])
x = inception_module(x, [64, 96, 128])
x = layers.MaxPooling2D((3,3), strides=(2,2), padding='same')(x)

# Second Inception module
x = inception_module(x, [128, 128, 192])
x = inception_module(x, [160, 160, 192])
x = inception_module(x, [160, 160, 192])
x = inception_module(x, [192, 192, 256])
x = layers.MaxPooling2D((3,3), strides=(2,2), padding='same')(x)

# Global average pooling and final classification layer
x = layers.GlobalAveragePooling2D()(x)
output_layer = layers.Dense(num_classes, activation='softmax')(x)

# Create the model and compile it
model = models.Model(inputs=input_layer, outputs=output_layer)

上升到了36%

# import tensorflow as tf
# from tensorflow.keras import layers, models, optimizers, activations, losses, metrics

def residual_block(x, filters, downsample=False):
    shortcut = x
    strides = (1, 1)
    if downsample:
        strides = (2, 2)
        shortcut = layers.Conv2D(filters, (1, 1), strides=strides, padding="same")(shortcut)
        shortcut = layers.BatchNormalization()(shortcut)
    x = layers.Conv2D(filters, (3, 3), strides=strides, padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = activations.relu(x)
    x = layers.Conv2D(filters, (3, 3), padding="same")(x)
    x = layers.BatchNormalization()(x)
    x = layers.add([x, shortcut])
    x = activations.relu(x)
    return x

def ResNet(input_shape, num_classes):
    input_layer = layers.Input(shape=input_shape)
    x = layers.Conv2D(32, (7, 7), strides=(2, 2), padding="same")(input_layer)
    x = layers.BatchNormalization()(x)
    x = activations.relu(x)
    x = layers.MaxPooling2D(pool_size=(3, 3), strides=(2, 2), padding="same")(x)

    x = residual_block(x, filters=32)
    x = residual_block(x, filters=32)

    x = residual_block(x, filters=64, downsample=True)
    x = residual_block(x, filters=64)

    x = residual_block(x, filters=128, downsample=True)
    x = residual_block(x, filters=128)

    x = residual_block(x, filters=256, downsample=True)
    x = residual_block(x, filters=256)

    x = layers.GlobalAveragePooling2D()(x)
    x = layers.Flatten()(x)
    x = layers.Dense(2048, activation="relu")(x)
    x = layers.Dropout(0.5)(x)
    x = layers.Dense(1024, activation="relu")(x)
    x = layers.Dropout(0.2)(x)
    output_layer = layers.Dense(num_classes, activation="softmax")(x)

    return models.Model(inputs=input_layer, outputs=output_layer)

input_shape = (176, 200, 3)
num_classes = 4
model = ResNet(input_shape, num_classes)

换ResNet网络,acc上升到44%,只能继续收集训练数据了,收集近千场数据后acc超过0.8


12.使用神经网络模型

import random
import sc2
import cv2
import numpy as np
import time


# import tensorflow as tf


class MyBotAI(sc2.BotAI):
    """
    自定义AI
    """

    def __init__(self, ENABLE_VIEW=False, USE_MODEL=False):
        super().__init__()
        # 最大工人数
        self.MAX_WORKERS = 64
        # 下一次进行决策的时间
        self.next_do_something_time = 0
        # 收集训练数据
        self.train_data = []
        # 是否打开cv视图
        self.ENABLE_VIEW = ENABLE_VIEW
        # 是否使用网络模型
        self.USE_MODEL = USE_MODEL
        if self.USE_MODEL:
            from tensorflow.keras import models
            self.model = models.load_model(
                r"A:\project\python\python-sc2\mybot\protoss\model\BaseCNNV1-128-epochs-0.0001-LR-STAGE1-0.44-valAcc")

    # 结束
    def on_end(self, game_result: sc2.Result):
        print("!!!!!!game over!!!!!!")
        print(game_result)
        if game_result == sc2.Result.Victory:
            np.save(r"A:\resources\train_data\sc2\protoss\v1\train\{}.npy".format(str(int(time.time()))),
                    np.array(self.train_data))

    async def on_step(self, iteration: int):
        """ 在每个游戏步骤上运行(在实时模式下循环)。 """
        # 将工人分配到所有基地。
        await self.distribute_workers()
        # 生产工人
        await self.build_works()
        # 建造水晶塔
        await self.build_pylons()
        # 建造瓦斯收集
        await self.build_assimilator()
        # 扩建基地
        await self.expand()
        # 建造兵营
        await self.build_barracks()
        # 建造军队
        await self.build_army()
        # 部队攻击
        await self.attack_enemy()
        # 绘制图像
        await self.view()
        # 侦察
        await self.lookFor()

    async def view(self):
        """ 根据open cv 绘制游戏的实时图像 """
        # 创建游戏图像矩阵
        game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

        # 绘制己方单位
        draw_dict = {
            sc2.UnitTypeId.NEXUS: [15, (0, 255, 0)],
            sc2.UnitTypeId.PYLON: [3, (25, 255, 25)],
            sc2.UnitTypeId.ASSIMILATOR: [2, (50, 255, 50)],
            sc2.UnitTypeId.GATEWAY: [3, (75, 255, 75)],
            sc2.UnitTypeId.CYBERNETICSCORE: [3, (100, 255, 100)],
            sc2.UnitTypeId.STARGATE: [5, (100, 255, 50)],
            sc2.UnitTypeId.ROBOTICSFACILITY: [5, (50, 255, 100)],
            sc2.UnitTypeId.PROBE: [1, (175, 255, 175)],
            sc2.UnitTypeId.VOIDRAY: [3, (255, 255, 0)],
            sc2.UnitTypeId.OBSERVER: [1, (255, 255, 255)],
        }

        for unit in draw_dict:
            for n in self.units(unit).ready:
                n_pos = n.position
                cv2.circle(game_data, (int(n_pos[0]), int(n_pos[1])), draw_dict[unit][0], draw_dict[unit][1], -1)

        # 绘制敌方的单元
        # 建筑
        main_base_names = ["nexus", "commandcenter", "hatchery"]
        for enemy_building in self.known_enemy_structures:
            pos = enemy_building.position
            # 非主基地
            if enemy_building.name.lower() not in main_base_names:
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (255, 50, 50), -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, (255, 0, 0), -1)
        # 敌方单位
        for enemy_unit in self.known_enemy_units:
            if not enemy_unit.is_structure:
                worker_names = ["probe",
                                "scv",
                                "drone"]
                pos = enemy_unit.position
                # 工人
                if enemy_unit.name.lower() in worker_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 100, 50), -1)
                # 军队
                else:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (255, 50, 100), -1)

        # 其他数据的可视化
        line_max = 50
        # 矿物数量/1500
        mineral_ratio = self.minerals / 1500
        if mineral_ratio > 1.0:
            mineral_ratio = 1.0
        # 瓦斯气的数量/1500
        vespene_ratio = self.vespene / 1500
        if vespene_ratio > 1.0:
            vespene_ratio = 1.0
        # 可用人口比例
        population_ratio = self.supply_left / (self.supply_cap + 1e-4)
        if population_ratio > 1.0:
            population_ratio = 1.0
        # 人口上限数/200
        plausible_supply = self.supply_cap / 200.0
        # voidray占全部人口的比例
        military_weight = len(self.units(sc2.UnitTypeId.VOIDRAY)) / ((self.supply_cap - self.supply_left) + 1e-4)
        if military_weight > 1.0:
            military_weight = 1.0
        # 绘制数据线line(图片, 起点, 终点, RGB, 粗细)
        # voidray占全部人口的比例
        cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
        # 人口上限数/200
        cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
        # 可用人口比例
        cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
        # 瓦斯气的数量/1500
        cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
        # 矿物数量/1500
        cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

        # 处理,翻转和缩放
        self.temp_data = cv2.flip(game_data, 0)
        # 是否可视化
        if self.ENABLE_VIEW:
            # 调整图像大小,原图像,输出图像所需大小,比例因子
            cv2.resize(self.temp_data, dsize=None, fx=2, fy=2)
            # 展示
            cv2.imshow("sc2 opencv view", self.temp_data)
            # 刷新
            cv2.waitKey(1)

    async def build_works(self):
        """ 建造工人 probe 数量不超过64,前期根据基地的数量动态生成"""
        if self.units(sc2.UnitTypeId.NEXUS).amount * 16 > self.units(sc2.UnitTypeId.PROBE).amount:
            if self.units(sc2.UnitTypeId.PROBE).amount < self.MAX_WORKERS:
                # 遍历完成的,没有队列的主基地 nexus
                for nexus in self.units(sc2.UnitTypeId.NEXUS).ready.idle:
                    # 判断能否建造
                    if self.can_afford(sc2.UnitTypeId.PROBE):
                        #  建造农民 train()命令单位训练另一个“单位”。
                        await self.do(nexus.train(sc2.UnitTypeId.PROBE))

    async def build_pylons(self):
        """ 建造水晶塔 """
        # 如果可用人口小于5并且没有正在建造的水晶塔就开始建造
        if self.supply_left < 5 and not self.already_pending(sc2.UnitTypeId.PYLON):
            # 先找到list<基地>
            nexuses = self.units(sc2.UnitTypeId.NEXUS).ready
            # 如果有基地并且可以造
            if nexuses.exists and self.can_afford(sc2.UnitTypeId.PYLON):
                await self.build(sc2.UnitTypeId.PYLON, near=nexuses.random)

    async def build_assimilator(self):
        """ 建造瓦斯收集器 """
        # 遍历所有可使用的基地
        for nexus in self.units(sc2.UnitTypeId.NEXUS).ready:
            # 基地附近的list<瓦斯>
            vespenes = self.state.vespene_geyser.closer_than(8, nexus)
            # 遍历所有瓦斯泉
            for vespene in vespenes:
                # 判断有余钱
                if self.can_afford(sc2.UnitTypeId.ASSIMILATOR):
                    worker = self.select_build_worker(vespene.position)
                    # 判断附件有工人并且瓦斯泉上没有建筑
                    if worker and not self.units(sc2.UnitTypeId.ASSIMILATOR).closer_than(1, vespene).exists:
                        await self.do(worker.build(sc2.UnitTypeId.ASSIMILATOR, vespene))

    async def expand(self):
        """ 根据当前基地总数扩张 对数函数"""
        if self.units(sc2.UnitTypeId.NEXUS).amount < self.game_time_of_minute() / 1.25 and \
                self.can_afford(sc2.UnitTypeId.NEXUS):
            await self.expand_now()

    async def build_barracks(self):
        """ 建造网关Gateway和控制核心Cybernetics Core """
        # 首先找到可用的水晶塔并且随机选择一个
        if self.units(sc2.UnitTypeId.PYLON).ready.exists:
            pylon = self.units(sc2.UnitTypeId.PYLON).ready.random
            # 存在网关就造1个核心,否则就造网关
            if self.units(sc2.UnitTypeId.GATEWAY).ready.exists and not self.units(sc2.UnitTypeId.CYBERNETICSCORE):
                # 可以支付的起并且没有正在建造的
                if not self.already_pending(sc2.UnitTypeId.CYBERNETICSCORE) \
                        and self.can_afford(sc2.UnitTypeId.CYBERNETICSCORE):
                    await self.build(sc2.UnitTypeId.CYBERNETICSCORE, near=pylon)
            # 建造网关
            # elif len(self.units(sc2.UnitTypeId.GATEWAY)) < self.game_time_of_minute() / 2 + 1:
            elif len(self.units(sc2.UnitTypeId.GATEWAY)) < 1:
                # 可以支付的起并且没有正在建造的
                if self.can_afford(sc2.UnitTypeId.GATEWAY) and not self.already_pending(sc2.UnitTypeId.GATEWAY):
                    await self.build(sc2.UnitTypeId.GATEWAY, near=pylon)

            # 建造星门
            if self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(sc2.UnitTypeId.GATEWAY)) < self.game_time_of_minute() / 2 + 1:
                    if self.can_afford(sc2.UnitTypeId.STARGATE) and not self.already_pending(sc2.UnitTypeId.STARGATE):
                        await self.build(sc2.UnitTypeId.STARGATE, near=pylon)

            # 建造侦察兵营地
            if self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.exists:
                if len(self.units(sc2.UnitTypeId.ROBOTICSFACILITY)) < 1:
                    if self.can_afford(sc2.UnitTypeId.ROBOTICSFACILITY) and \
                            not self.already_pending(sc2.UnitTypeId.ROBOTICSFACILITY):
                        await self.build(sc2.UnitTypeId.ROBOTICSFACILITY, near=pylon)

    async def build_army(self):
        """ 建造 Stalker """
        # 遍历所有已经建好的且没有队列的Gateway
        # for gw in self.units(sc2.UnitTypeId.GATEWAY).ready.idle:
        #     # 如果能支付得起且可用人口大于2,则训练Stalker
        #     if self.units(sc2.UnitTypeId.STALKER).amount <= self.units(sc2.UnitTypeId.VOIDRAY).amount:
        #         if self.can_afford(sc2.UnitTypeId.STALKER) and self.supply_left > 2:
        #             await self.do(gw.train(sc2.UnitTypeId.STALKER))
        # 遍历所有的星门
        for sg in self.units(sc2.UnitTypeId.STARGATE).ready.idle:
            if self.can_afford(sc2.UnitTypeId.VOIDRAY) and self.supply_left > 4:
                await self.do(sg.train(sc2.UnitTypeId.VOIDRAY))

    async def attack_enemy(self):
        """ 随机攻击的方法 """
        # 如果voidray有闲置的
        if self.units(sc2.UnitTypeId.VOIDRAY).idle.amount > 0:
            choice_dict = {0: "等待", 1: "攻击敌方单位", 2: "攻击对方建筑", 3: "攻击对方出生地"}

            if self.USE_MODEL:
                # 根据图像预测结果
                prediction = self.model.predict(self.temp_data.reshape((-1, 176, 200, 3)))
                choice = np.argmax(prediction)
                msg = "{}--机器学习预测的当前最佳策略为: {}---{}".format(round(self.game_time_of_minute(), 2), choice,
                                                                         choice_dict[choice])
                print(msg)
            else:
                # 随机一种决策[0, 1, 2, 3]
                choice = random.randrange(0, 4)
                msg = "{}--随机策略为: {}---{}".format(round(self.game_time_of_minute(), 2), choice,
                                                       choice_dict[choice])
                print(msg)
            # 定义目标
            target = False
            # 如果当前时间大于定义的时间
            if self.game_time_of_minute() > self.next_do_something_time:
                # 不攻击,等待
                if choice == 0:
                    wait = random.randrange(8, 24) / 60
                    self.next_do_something_time = self.game_time_of_minute() + wait
                    # target = self.units(sc2.UnitTypeId.NEXUS).random
                # 攻击离家最近的敌方单位
                elif choice == 1:
                    if len(self.known_enemy_units) > 0:
                        target = self.known_enemy_units.closest_to(random.choice(self.units(sc2.UnitTypeId.NEXUS)))
                # 攻击敌方建筑物
                elif choice == 2:
                    if len(self.known_enemy_structures) > 0:
                        target = random.choice(self.known_enemy_structures)
                # 攻击敌方出生地
                elif choice == 3:
                    target = self.enemy_start_locations[0]

                # 如果目标不为空,则让所有闲置的voidray做该决策
                if target:
                    for vr in self.units(sc2.UnitTypeId.VOIDRAY).idle:
                        if choice != 0:
                            await self.do(vr.attack(target))
                        # else:
                        #     await self.do(vr.move(target))
                # 每次做出决策,就将其连同当前可视化信息添加到训练集中,列如[(1,0,0,0),(...)]
                y = np.zeros(4)
                y[choice] = 1
                self.train_data.append([y, self.temp_data])

                if self.game_time_of_minute() > 30:
                    1 / 0

                await self.chat_send(msg)

    def find_enemy_target(self):
        """ 寻找敌方目标 敌方单位 -》 敌方建筑物 -》 敌方出生点 """
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        else:
            return self.enemy_start_locations[0]

    def game_time_of_minute(self):
        """ 游戏分钟数 """
        return self.time / 60.0 + 1e-8

    def research_science(self):
        """ 研究科技 """
        for rs in self.units(sc2.UnitTypeId.CYBERNETICSCORE).ready.idle:
            self.do(rs.research(sc2.AbilityId.CYBERNETICSCORERESEARCH_PROTOSSAIRWEAPONSLEVEL1))

    async def lookFor(self):
        """ 侦察敌方情况 """
        # 有则行动
        if len(self.units(sc2.UnitTypeId.OBSERVER)) > 0:
            scout = self.units(sc2.UnitTypeId.OBSERVER)[0]
            # 闲置的侦察兵
            if scout.is_idle:
                enemy_location = self.enemy_start_locations[0]
                # 侦察敌人出生地附近的位置
                move_to = self.random_location_variance(enemy_location)
                await self.do(scout.move(move_to))
        # 没有则建造
        else:
            for rf in self.units(sc2.UnitTypeId.ROBOTICSFACILITY).ready.idle:
                if self.can_afford(sc2.UnitTypeId.OBSERVER) and self.supply_left > 0:
                    await self.do(rf.train(sc2.UnitTypeId.OBSERVER))

    def random_location_variance(self, enemy_start_location):
        """ 随机选取敌方附近的位置 """
        x = enemy_start_location[0]
        y = enemy_start_location[1]

        x += ((random.randrange(-32, 32)) / 100) * enemy_start_location[0]
        y += ((random.randrange(-32, 32)) / 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 = sc2.position.Point2(sc2.position.Pointlike((x, y)))
        return go_to

import os
import random
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from mybot.protoss.normal import MyBotAI
from examples.zerg.zerg_rush import ZergRushBot
from multiprocessing.pool import Pool

# 游戏地图
map_name = "AbyssalReefLE"
# 模式开关
target = 0
# 进程数量
process_core = 4
# 任务队列数
task_num = 1024

# 种族数组
race_arr = [Race.Protoss, Race.Zerg, Race.Terran]

# 比赛难度
hard_arr = [
    # Difficulty.Easy,
    Difficulty.Medium,
    Difficulty.MediumHard,
    Difficulty.Hard,
    # Difficulty.Harder
]


def stand(real=False):
    """ 标准模式 """
    run_game(maps.get(map_name), [
        Bot(Race.Protoss, MyBotAI(ENABLE_VIEW=True, USE_MODEL=True)),
        Computer(random.choice(race_arr), random.choice(hard_arr))
    ], realtime=real)


def run(index):
    """ 多进程模式 收集数据"""
    print(f"子进程{index}的id为{os.getpid()}")
    run_game(maps.get(map_name), [
        Bot(Race.Protoss, MyBotAI()),
        Computer(random.choice(race_arr), random.choice(hard_arr))
    ], realtime=False)


if __name__ == '__main__':
    if target == 0:
        stand(False)
    else:
        p = Pool(process_core)
        for i in range(task_num):
            p.apply_async(run, args=(i, ))
        p.close()
        p.join()
        print("process over")

在这里插入图片描述
在这里插入图片描述

可以看到只训练了四种攻击方式,这里很轻松的战胜了艰难(harder)

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值