Python AI в StarCraft II. Часть III: гейзеры и экспансия

Предыдущая статья — Python AI в StarCraft II. Часть II: рабочие и пилоны.

В третьей части серии статей про использование AI в игре Starcraft II мы займёмся добычей другого очень важного ресурса — газа. А также произведем экспансию в другие регионы.

В конце концов наша область истощится. Не забывайте, что нам нужно всего трое рабочих на один участок с полезными ископаемыми. В итоге нам нужно расширяться. Однако к расширению не следует относиться слишком легкомысленно. При расширении вы ослабляете свою защиту. Тем не менее, когда мы истощим нашу область, нам придется задуматься о расширении. К счастью, это для нас несложная задача, поскольку некоторая логика расширения уже для нас реализована. Давайте добавим add await self.expand() в наш метод on_step:

    async def on_step(self, iteration):
        # what to do every step
        await self.distribute_workers()  # in sc2/bot_ai.py
        await self.build_workers()  # workers bc obviously
        await self.build_pylons()  # pylons are protoss supply buildings
        await self.expand()  

Теперь наш метод expand имеет следующий вид:

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

В приведенном выше случае мы ограничиваем наши расширения только двумя локациями. Rод для метода expand_now можно найти в файле sc2 / bot_ai.py.

Наш полный код на данный момент выглядит следующим образом:

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer

from sc2.constants import NEXUS, PROBE, PYLON

class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration):
        # what to do every step
        await self.distribute_workers()  # in sc2/bot_ai.py
        await self.build_workers()  # workers bc obviously
        await self.build_pylons()  # pylons are protoss supply buildings
        await self.expand()


    async def build_workers(self):
        # nexus = command center
        for nexus in self.units(NEXUS).ready.noqueue:
            # we want at least 20 workers, otherwise let's allocate 70% of our supply to workers.
            # later we should use some sort of regression algo maybe for this?

            if self.can_afford(PROBE):
                await self.do(nexus.train(PROBE))

    async def build_pylons(self):
        if self.supply_left &lt; 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 expand(self):
        if self.units(NEXUS).amount &lt; 2 and self.can_afford(NEXUS):
            await self.expand_now()


run_game(maps.get("AbyssalReefLE"), [
    Bot(Race.Protoss, SentdeBot()),
    Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

Вот теперь-то мы развернемся! У нас есть 2 зоны командного центра и быстрорастущая коллекция ресурсов. Далее нужно сосредоточиться на том, что нам требуется для создания нашей армии (о ней речь пойдет в следующем уроке).

[machinelearning_ad_block]

Когда мы приблизимся к нашей цели собрать армию и атаковать нашего врага, мы обнаружим, что нам понадобится топливо (газ). Газ собирают из гейзеров Веспен, разбросанных по карте. Для расы протоссов здание, необходимое для сбора этого газа, называется ассимилятором (класс Assimilator). Нам нужно построить его прямо над гейзерами.

Чтобы произвести постройку, нам надо импортировать класс Assimilator. Поэтому давайте для начала добавим его в наш стандартный импорт:

from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR

Теперь давайте добавим await self.build_assimilator() в наш метод on_step, после чего он приобретет следующий вид:

class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration):
        await self.distribute_workers()  # in sc2/bot_ai.py
        await self.build_workers()  # workers bc obviously
        await self.build_pylons()  # pylons are protoss supply buildings
        await self.expand()  # expand to a new resource area.
        await self.build_assimilator()  # getting gas

А теперь нам нужно написать этот метод. Как вы, вероятно, догадываетесь, найти правильный гейзер и построить прямо на нем здание может оказаться серьезной проблемой для написания кода. К счастью для нас, большая часть этой работы была уже сделана.

Например, мы можем найти доступные гейзеры рядом с нашим Нексусом с помощью API. У нас есть по одному Нексусу на каждую ресурсную область — так мы и будем их находить.

Затем нам также нужно подобрать для этого одного из наших рабочих. Чтобы не терять много времени, нужно взять ближайшего. Это тоже уже сделано за нас.

Наконец, мы можем построить что-то, обратившись к рабочему юниту. Для этого нужно просто выполнить .build (THING, WHERE).

Приступим.

Сначала мы хотим перебрать все наши нексусы, а затем все васпены (другое название для месторождений газа в Starcraft II), находящиеся в радиусе ~ 25 единиц от нексуса:

    async def build_assimilator(self):
        for nexus in self.units(NEXUS).ready:
            vaspenes = self.state.vespene_geyser.closer_than(25.0, nexus)

Отлично, мы нашли васпены. Теперь мы хотим строить. Как обычно, если у нас нет необходимых средств, строить мы не можем:

    async def build_assimilator(self):
        for nexus in self.units(NEXUS).ready:
            vaspenes = self.state.vespene_geyser.closer_than(25.0, nexus)
            for vaspene in vaspenes:
                if not self.can_afford(ASSIMILATOR):
                    break

В противном случае нам нужно выбрать доступного рабочего:

    async def build_assimilator(self):
        for nexus in self.units(NEXUS).ready:
            vaspenes = self.state.vespene_geyser.closer_than(25.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 build_assimilator(self):
        for nexus in self.units(NEXUS).ready:
            vaspenes = self.state.vespene_geyser.closer_than(25.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))

А полный код теперь имеет вот такой вид:

import sc2
from sc2 import run_game, maps, Race, Difficulty
from sc2.player import Bot, Computer
from sc2.constants import NEXUS, PROBE, PYLON, ASSIMILATOR


class SentdeBot(sc2.BotAI):
    async def on_step(self, iteration):
        await self.distribute_workers()  # in sc2/bot_ai.py
        await self.build_workers()  # workers bc obviously
        await self.build_pylons()  # pylons are protoss supply buildings
        await self.expand()  # expand to a new resource area.
        await self.build_assimilator()  # getting gas

    async def build_workers(self):
        # nexus = command center
        for nexus in self.units(NEXUS).ready.noqueue:
            # we want at least 20 workers, otherwise let's allocate 70% of our supply to workers.
            # later we should use some sort of regression algo maybe for this?

            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 expand(self):
        if self.units(NEXUS).amount < 2 and self.can_afford(NEXUS):
            await self.expand_now()

    async def build_assimilator(self):
        for nexus in self.units(NEXUS).ready:
            vaspenes = self.state.vespene_geyser.closer_than(25.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))


run_game(maps.get("AbyssalReefLE"), [
    Bot(Race.Protoss, SentdeBot()),
    Computer(Race.Terran, Difficulty.Easy)
], realtime=False)

Запустив его, мы можем убедиться, что идет строительство ассимиляторов:

vespene geyser assimilator

Потрясающе! Теперь давайте займемся созданием армии! Но уже в следующей статье.

Следующая статья — Python AI в StarCraft II. Часть IV: создание AI армии.