ML

Python AI в StarCraft II. Часть IV: создание AI-армии

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

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

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

Какие юниты мы будет создавать? Сколько? Юниты истощают наши запасы полезных ископаемых и топлива. Также может истощаться и сама популяция юнитов. И наконец, пожалуй, самый главный элемент игры — это время.

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

Мы, конечно, не эксперты, но немного поискав, мы обнаружили пару юритов, пригодных для ведения массивных военных операций. Это Сталкер (Stalker) и Луч Бездны (Void Ray).

Сталкер — наземный юнит, но также может принимать участие и в воздушных операциях. А Луч Смерти — воздушный, но по аналогии тоже может участвовать в наземных столкновениях. Луч Бездны несколько сложней в производстве, и, соответственно, оно займет больше времени. На данный момент проще создать Сталкера, поэтому мы им и займемся.

С этого момента игра, как и всякая стратегия, становится весьма сложной, так что держитесь! Зайдя еще раз на страницу, где описаны все юниты Протоссов, мы обнаружили, что путь к Сталкеру следующий: Нексус (Nexus) > Врата (Gateway) > Кибернетическое ядро (Cybernetics Core) > Сталкер (Stalker). Что же, вполне понятно. Обратите внимание, что Кибернетическое Ядро больше связано с исследованиями. Свои боевые единицы вы по-прежнему будете производить из Врат. Кибернетическое Ядро вам понадобится только одно (и надеемся, что оно не будет уничтожено), а вот Врат, вероятно, потребуется несколько — для ускорения процесса создания юнитов.

Наш код на настоящий момент имеет вот такой вид:

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)

Теперь, чтобы начать, нам нужно создать необходимые строения. Любые новые здания нужно строить в пределах псионической матрицы, которая создана нашими пилонами. Поэтому все будем строить рядом с пилонами. Итак, возьмем любого пилона. Если у нас уже есть Врата, но нет Кибернетического Ядра, то его надо создать. Если нет Врат, то создадим сначала их.

[machinelearning_ad_block]

Добавим следующий код в наш метод on_step:

class SentdeBot(sc2.BotAI):
    async def on_step(self, 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()

Теперь нам необходимо создать метод offensive_force_buildings. Сначала поищем ближайший пилон для строительства:

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

Теперь, если есть Врата, давайте создадим Кибернетическое Ядро:

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

Готово. А если нам все же нужно было построить Врата, то вот код для них:

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

Теперь мы можем начать строить нашу армию. Давайте добавим еще один метод в наш метод on_step:

class SentdeBot(sc2.BotAI):
    async def on_step(self, 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()

Далее, если у нас есть ресурсы и мы можем себе позволить такие затраты, приступим к строительству юнитов для нашей армии:

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

Полный код сейчас имеет следующий вид:

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


class SentdeBot(sc2.BotAI):
    async def on_step(self, 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()

    async def build_workers(self):
        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 < 3 and self.can_afford(NEXUS):
            await self.expand_now()

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

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


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

Теперь наша армия начинает формироваться:

На данный момент мы готовы приступить к командованию армией, чем мы и займемся в следующей статье.

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

Ilyaragalin

Recent Posts

7 наилучших библиотек визуализации Python на 2024 год

Python предлагает набор библиотек, удовлетворяющих различные потребности в визуализации, будь то академические исследования, бизнес-аналитика или…

2 дня ago

Как преобразовать строку в байты в Python

В Python для представления данных в двоичной форме можно использовать байты. Из этой статьи вы…

2 недели ago

Что такое Werkzeug?

В этой статье рассказывается о том, что такое Werkzeug и как Flask использует его для…

2 недели ago

Как прибавить дни, месяцы и годы к дате в Python

При работе с датами часто возникает необходимость прибавлять к дате или вычитать из нее различные…

3 недели ago

Социальная аутентификация в приложении на Flask

В этом руководстве мы рассмотрим, как добавить социальную аутентификацию с помощью GitHub и Google в…

1 месяц ago

Проверка типов в Python

В этой статье мы рассмотрим, что такое подсказки типов и чем они могут быть полезны.…

1 месяц ago