Python编写AI机器人玩爆星际争霸系列(6)

timg

上一节中我们通过建造多个星门(神族兵营)生产Stalker(追猎者)可以打败简单甚至中等级别的电脑,但是对战困难级别的电脑时候,我们的程序就无法取胜了。今天我们就开始做下尝试,通过改进我们程序来打败困难级别的电脑。

在这之前,我们回顾一下之前的代码中有哪些主要的问题?和可以改进的地方?

首先,我们注意到之前的代码中并没有考虑到一个因素:时间。打个比方,实际在对战过程中,在开局五分钟的时候我们有3个兵营,可以生成出的作战单位可以足够对抗敌方(困难电脑),但是过了15分钟我方还仍然是3个兵营,而我们的采集的矿物显然足够建造更多的兵营,这样可以产出更多作战单位。当然我们需要根据战局的时间推进调整我们资源采集、训练作战单位和升级科技(建造科技建筑)三者之间的关系。如果我们采用快攻战术,我们前期不需要扩张太大(建造太多的基地Nexus),如果是采取运营的战术,前期就可以快速扩张,同时仅建造少量的兵营建筑,产出作战单位进行防守即可。这些战术的实现都和时间这个维度关系密切。

其次,我们需要注意工人和作战单位的比例,一般我们3个矿情况下,工人数量无需超过60,大家肯定还记得之前我们提到过,在星际争霸中,一个晶体矿脉上有3个工人就处于非常饱和的状态,所以加上气矿所需要的工人,一般一个矿需要农民在20左右。这样我们就需要限定一下工人的生成数量,这样可以让更多的资源投入到科技升级和作战单位的生产。同时也减少人口占用,增加作战部队。

最后,单一的兵种在星际争霸这个游戏中对抗在后期是非常不利的,因为兵种之间会有很多相互克制,比如追猎者在面对人族的重装掠劫者时候就会被克制,对敌方的伤害很小,而自己被攻击时伤害却有加成。所以我们需要进行兵种的组合,今天我们就尝试加上神族的另一个强力兵种:虚空辉光舰(void ray)。

根据上面的三个思路,我们一步一步来改造我们的程序。

这里我们需要引入一个线性模型,估算一下可能在游戏中,每秒大约可以设定为165个iteration,这样我们就

   def __init__(self):
        self.ITERATIONS_PER_MINUTE = 165

同时在on_step方法中使用iteration:

    async def on_step(self, iteration):
        self.iteration = iteration
        ...

这样,我们就可以实现随着时间的推进我们可以建造更多的Gateway(星门,兵营建筑),这里修改offensive_force_buildings方法为如下:

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

完整的offensive_force_buildings方法如下:

    async def offensive_force_buildings(self):
        if self.units(PYLON).ready.exists:
            pylon = self.units(PYLON).ready.random

            if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
                if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
                    await self.build(CYBERNETICSCORE, near=pylon)

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

同时我们需要限定一下最大工人数量:

    def __init__(self):
        self.ITERATIONS_PER_MINUTE = 165
        self.MAX_WORKERS = 60

这样在 build_workers方法中进行下面调整:

if (len(self.units(NEXUS)) * 16) > len(self.units(PROBE)) and len(self.units(PROBE)) < self.MAX_WORKERS:

这样我们build_workders方法改为这样:

   async def build_workers(self):
        if (len(self.units(NEXUS)) * 16) > len(self.units(PROBE)) and len(self.units(PROBE)) < self.MAX_WORKERS:
            for nexus in self.units(NEXUS).ready.noqueue:
                if self.can_afford(PROBE):
                    await self.do(nexus.train(PROBE))

另外,我们之前扩张基地的逻辑是只要有足够的矿物就扩张新的基地,这个策略也可改进一下,改为每分钟进行扩张,这样就可以建造足够的作战单位。平衡工人和作战单位数量:

    async def expand(self):
        if self.units(NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and self.can_afford(NEXUS):
            await self.expand_now()

最后我们需要加入虚空辉光舰(void ray)作战兵种,我们需要引入STARGATE(生成虚空辉光舰的兵营建筑)和VOIDRAY

from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR, GATEWAY, \
 CYBERNETICSCORE, STALKER, STARGATE, VOIDRAY

然后增加建造 offensive force buildings方法:

async def offensive_force_buildings(self):
        print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(PYLON).ready.exists:
            pylon = self.units(PYLON).ready.random

            if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
                if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
                    await self.build(CYBERNETICSCORE, near=pylon)

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

            if self.units(CYBERNETICSCORE).ready.exists:
                if len(self.units(STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE)/2):
                    if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
                        await self.build(STARGATE, near=pylon)

同时需要增加生产虚空的方法:

async def build_offensive_force(self):
        for gw in self.units(GATEWAY).ready.noqueue:
            if not self.units(STALKER).amount > self.units(VOIDRAY).amount:

                if self.can_afford(STALKER) and self.supply_left > 0:
                    await self.do(gw.train(STALKER))

        for sg in self.units(STARGATE).ready.noqueue:
            if self.can_afford(VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(VOIDRAY))

因为Stalker的生产速度比虚空快,而且更便宜。所以我们通常需要建造更多的Stalker。
最后我们调整下进攻的方法:

async def attack(self):
        # {UNIT: [战斗数量, 防守数量]}
        aggressive_units = {STALKER: [15, 5],
                            VOIDRAY: [8, 3]}


        for UNIT in aggressive_units:
            if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][1]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target(self.state)))

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

 

这样我们完整的代码如下:

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR, GATEWAY, \
 CYBERNETICSCORE, STALKER, STARGATE, VOIDRAY
import random


class MrBot(sc2.BotAI):
    def __init__(self):
        self.ITERATIONS_PER_MINUTE = 165
        self.MAX_WORKERS = 50

    async def on_step(self, iteration):
        self.iteration = iteration
        await self.distribute_workers()
        await self.build_workers()
        await self.build_pylons()
        await self.build_assimilators()
        await self.expand()
        await self.offensive_force_buildings()
        await self.build_offensive_force()
        await self.attack()

    async def build_workers(self):
        if (len(self.units(NEXUS)) * 16) > len(self.units(PROBE)) and len(self.units(PROBE)) < self.MAX_WORKERS:
            for nexus in self.units(NEXUS).ready.noqueue:
                if self.can_afford(PROBE):
                    await self.do(nexus.train(PROBE))


    async def build_pylons(self):
        if self.supply_left < 5 and not self.already_pending(PYLON):
            nexuses = self.units(NEXUS).ready
            if nexuses.exists:
                if self.can_afford(PYLON):
                    await self.build(PYLON, near=nexuses.first)

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

    async def expand(self):
        if self.units(NEXUS).amount < (self.iteration / self.ITERATIONS_PER_MINUTE) and self.can_afford(NEXUS):
            await self.expand_now()

    async def offensive_force_buildings(self):
        #print(self.iteration / self.ITERATIONS_PER_MINUTE)
        if self.units(PYLON).ready.exists:
            pylon = self.units(PYLON).ready.random

            if self.units(GATEWAY).ready.exists and not self.units(CYBERNETICSCORE):
                if self.can_afford(CYBERNETICSCORE) and not self.already_pending(CYBERNETICSCORE):
                    await self.build(CYBERNETICSCORE, near=pylon)

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

            if self.units(CYBERNETICSCORE).ready.exists:
                if len(self.units(STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE)/2):
                    if self.can_afford(STARGATE) and not self.already_pending(STARGATE):
                        await self.build(STARGATE, near=pylon)

    async def build_offensive_force(self):
        for gw in self.units(GATEWAY).ready.noqueue:
            if not self.units(STALKER).amount > self.units(VOIDRAY).amount:
                if self.can_afford(STALKER) and self.supply_left > 0:
                    await self.do(gw.train(STALKER))

        for sg in self.units(STARGATE).ready.noqueue:
            if self.can_afford(VOIDRAY) and self.supply_left > 0:
                await self.do(sg.train(VOIDRAY))

    def find_target(self, state):
        if len(self.known_enemy_units) > 0:
            return random.choice(self.known_enemy_units)
        elif len(self.known_enemy_structures) > 0:
            return random.choice(self.known_enemy_structures)
        else:
            return self.enemy_start_locations[0]

    async def attack(self):
        # {UNIT: [进攻单位, 留守单位]}
        aggressive_units = {STALKER: [15, 5],
                            VOIDRAY: [8, 3]} 


        for UNIT in aggressive_units:
            if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][1]:
                for s in self.units(UNIT).idle:
                    await self.do(s.attack(self.find_target(self.state)))

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


run_game(maps.get("(2)CatalystLE"), [
    Bot(Race.Protoss, MrBot()),
    Computer(Race.Terran, Difficulty.Hard)
    ], realtime=False)

 

执行这个代码,启动游戏,和困难电脑对战:

1 2 3

 

喊一声,虚空爸爸!

未经允许不得转载:Mr.开发者 » Python编写AI机器人玩爆星际争霸系列(6)

赞 (1)
分享到:更多 ()