In this episode we review the changes I have made from the last version of the bot and we explore the data acquisition part.

So first of all, in the last episode I was losing to early rushes of zerglings so I tried to produce a few adepts as a buffer for my stalkers. As a matter of fact you need a cybernetics core to produce adepts, which I forgot, making this strategy utterly useless. I therefore switched back to zealots, I produce now two or three of them in the begining of the game and send them to attack the natural of the opponent. This simple move permits me usually to kill a few units and mess with the AI of my opponent. In some cases it evens kills 10+ drones when the opponent is greedy.

async def build_units(self):
#for each gw that is not producing units
for gw in self.structures(UnitTypeId.GATEWAY).ready:
#if we can afford it and if we have enough supply left and the CC is not yet done, we produce a few zealots
if self.can_afford(UnitTypeId.ZEALOT) and not self.structures(UnitTypeId.CYBERNETICSCORE).ready and gw.is_idle and self.units(UnitTypeId.ZEALOT).amount < 2:
gw.train(UnitTypeId.ZEALOT)
#And
else:
#our defense/early harass mechanic with our zealots
for zealot in self.units(UnitTypeId.ZEALOT).ready.idle:
if enemy_fighters:
self.do(zealot.attack(random.choice(enemy_fighters)))
else:
self.do(zealot.attack(random.choice(self.ordered_expansions[0:3])))
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Next I implemented dark templars which are coming a few minutes after the zealots. As they are invisble it directly kills some additional unevolved AIs.

#Method to warp units
async def warp_new_units_DT(self):
for warpgate in self.structures(UnitTypeId.WARPGATE).ready:
#We take the abilities of the warpgate to retrieves the warping stalker ability
abilities = await self.get_available_abilities(warpgate)
#if it is not on cooldown
if AbilityId.WARPGATETRAIN_DARKTEMPLAR in abilities and self.units(UnitTypeId.DARKTEMPLAR).amount < 3:
#we sort the pylons by their distance to our warp gate
self.ordered_pylons = sorted(self.structures(UnitTypeId.PYLON).ready, key=lambda pylon: pylon.distance_to(warpgate))
#we pick the pylon the further away from the warp gate, because why not
pos = self.ordered_pylons[-1].position.random_on_distance(4)
#we select the placement of the stalker we want to warp as the position of the pylon selected
placement = await self.find_placement(AbilityId.WARPGATETRAIN_DARKTEMPLAR , pos, placement_step=1)
#if no placement available we return error
if placement is None:
print("can't place")
return
#else we warp our stalker !
warpgate.warp_in(UnitTypeId.DARKTEMPLAR , placement)
async def send_dark_temp(self):
enemies = self.enemy_units.filter(lambda unit: unit.type_id not in {UnitTypeId.LARVA, UnitTypeId.EGG})
enemy_fighters = enemies.filter(lambda u: u.can_attack) + self.enemy_structures(
{UnitTypeId.BUNKER, UnitTypeId.SPINECRAWLER, UnitTypeId.PHOTONCANNON}
)
for dt in self.units(UnitTypeId.DARKTEMPLAR).ready.idle:
if enemy_fighters:
self.do(dt.attack(random.choice(enemy_fighters)))
else:
self.do(dt.attack(random.choice(self.ordered_expansions[0:3])))
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Then I figured that I was losing some matches when my opponent was expending in far positions, my army was simply not scouting there and i was running out of minerals and eventually losing as I was not attacking those far positions and my stalker army was just chilling in the opponent base. First I added a method to produce observers and scout every expand on the map. But I was still finishing some matches in a Tie as some my opponents were building bases outside of the reach of my observers. So I added a random factor in the movement of my observers and finally could add a small chance to scout those “out of position” buildings.

#Method to send our obs scouting
async def send_obs(self):
#We retrieve the list of possible extensions
self.ordered_expansions = None
self.ordered_expansions = sorted(
self.expansion_locations.keys(), key=lambda expansion: expansion.distance_to(self.enemy_start_locations[0])
)
#We send the obs in each, initially I limited at 4 but then in some games I could not find a hidden expo..
for obs in self.units(UnitTypeId.OBSERVER).ready:
if obs.is_idle:
location=random.choice(self.ordered_expansions[0:]).position.random_on_distance(10)
self.do(obs.move(location))
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

I also added a new method that would send the whole army looking for the opponent when I pass 150 supply. Which is kind of rare as my bot fights all the time !

async def clear_map(self):
if self.supply_used > 150:
army = self.units.filter(lambda unit: unit.type_id in {UnitTypeId.COLOSSUS, UnitTypeId.STALKER, UnitTypeId.DARKTEMPLAR, UnitTypeId.ZEALOT})
ground_enemies = self.enemy_units.filter(
lambda unit: not unit.is_flying and unit.UnitTypeId not in {UnitTypeId.LARVA, UnitTypeId.EGG}
)
# we dont see anything so start to clear the map
if not ground_enemies:
for unit in army:
# clear found structures
if self.enemy_structures:
# focus down low hp structures first
in_range_structures = self.enemy_structures.in_attack_range_of(unit)
if in_range_structures:
lowest_hp = min(in_range_structures, key=lambda e: (e.health + e.shield, e.tag))
if unit.weapon_cooldown == 0:
self.do(unit.attack(lowest_hp))
else:
# dont go closer than 1 with roaches to use ranged attack
if unit.ground_range > 1:
self.do(unit.move(lowest_hp.position.towards(unit, 1 + lowest_hp.radius)))
else:
self.do(unit.move(lowest_hp.position))
else:
self.do(unit.move(self.enemy_structures.closest_to(unit)))
# check bases to find new structures
else:
self.do(unit.move(self.army_target))
return
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

I also added a few other things. I now create colossus later in the game and try to expand up to 4 bases if the game goes on. I also research additional upgrades and try to chronoboost all those things in a good order, which is difficult. For a long time my chronoboost was right back up when my cybernetics core was poping, wasting a full chrono on the nexus instead of speeding up warp gate research. So I just added a few seconds of delay on the chrono boost when it targets the nexus.

Finally I added some colossus for the late game. But to be frank I never reached late game in any of my games as they are mostly over before the 12 minutes mark.

And last but not least I added a method to extract data from the game ! If we want our bot to learn from a deep neural network we need to extract the data of the game somehow and pass it as an image to our neural network. I used the opencv library to draw a map of the game at every step that will show our building, our units and same for our opponent.

#Draw all info on map
async def intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
draw_dict = {
NEXUS: [(0, 255, 0)],
PYLON: [(10, 255, 0)],
DARKSHRINE: [(20, 255, 0)],
ASSIMILATOR: [(30, 255, 0)],
GATEWAY: [(40, 255, 0)],
CYBERNETICSCORE: [(50, 255, 0)],
STARGATE: [(60, 255, 0)],
ROBOTICSFACILITY: [(70, 255, 0)],
WARPGATE: [(80, 255, 0)],
FORGE: [(90, 255, 0)],
ROBOTICSBAY: [(100, 255, 0)],
TWILIGHTCOUNCIL: [(110, 255, 0)],
COLOSSUS: [(0, 255, 10)],
ZEALOT: [(0, 255, 20)],
STALKER: [(0, 255, 30)],
OBSERVER: [(0, 255, 40)],
DARKTEMPLAR: [(0, 255, 50)],
PROBE: [(0, 255, 60)],
VOIDRAY: [(0, 255, 70)],
HATCHERY: [(255, 0, 5)],
EXTRACTOR: [(255, 0, 6)],
SPAWNINGPOOL: [(255, 0, 7)],
EVOLUTIONCHAMBER: [(255, 0, 8)],
HYDRALISKDEN: [(255, 0, 9)],
SPIRE: [(255, 0, 10)],
ULTRALISKCAVERN: [(255, 0, 11)],
INFESTATIONPIT: [(255, 0, 12)],
NYDUSNETWORK: [(255, 0, 13)],
BANELINGNEST: [(255, 0, 14)],
ROACHWARREN: [(255, 0, 15)],
SPINECRAWLER: [(255, 0, 16)],
SPORECRAWLER: [(255, 0, 17)],
LAIR: [(255, 0, 18)],
HIVE: [(255, 0, 19)],
GREATERSPIRE: [(255, 0, 20)],
DRONE: [(255, 0, 21)],
ZERGLING: [(255, 0, 22)],
OVERLORD: [(255, 0, 23)],
HYDRALISK: [(255, 0, 24)],
MUTALISK: [(255, 0, 25)],
ULTRALISK: [(255, 0, 26)],
ROACH: [(255, 0, 27)],
INFESTOR: [(255, 0, 28)],
CORRUPTOR: [(255, 0, 29)],
BROODLORDCOCOON: [(255, 0, 30)],
BROODLORD: [(255, 0, 31)],
QUEEN: [(255, 0, 32)],
OVERSEER: [(255, 0, 33)]
}
army = self.units
ground_enemies = self.enemy_units
for unit_type in draw_dict:
for unit in self.units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius), draw_dict[unit_type][0], -1)
for structure_type in draw_dict:
for structure in self.structures(structure_type).ready:
pos = structure.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(structure.radius), draw_dict[structure_type][0], -1)
if self.enemy_units:
for unit_type in draw_dict:
for unit in self.enemy_units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius), draw_dict[unit_type][0], -1)
if self.enemy_structures:
for structure_type in draw_dict:
for structure in self.enemy_structures(structure_type).ready:
pos = structure.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(structure.radius), draw_dict[structure_type][0], -1)
for mineral in self.mineral_field:
pos = mineral.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(mineral.radius),(236, 248, 248) , -1)
for vespene in self.vespene_geyser:
pos = vespene.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(vespene.radius),(180, 227, 204) , -1)
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)
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

I think the result looks quite nice :

If we wanted to then save the data along some information about what the bot has chosen to do at each image we could imagine something along :

async def attack(self):
if len(self.units(VOIDRAY).idle) > 0:
choice = random.randrange(0, 4)
target = False
if self.iteration > self.do_something_after:
if choice == 0:
# no attack
wait = random.randrange(20, 165)
self.do_something_after = self.iteration + wait
elif choice == 1:
#attack_unit_closest_nexus
if len(self.known_enemy_units) > 0:
target = self.known_enemy_units.closest_to(random.choice(self.units(NEXUS)))
elif choice == 2:
#attack enemy structures
if len(self.known_enemy_structures) > 0:
target = random.choice(self.known_enemy_structures)
elif choice == 3:
#attack_enemy_start
target = self.enemy_start_locations[0]
if target:
for vr in self.units(VOIDRAY).idle:
await self.do(vr.attack(target))
y = np.zeros(4)
y[choice] = 1
print(y)
#We append the choice we made to the training_data
self.train_data.append([y,self.flipped])
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

This would save the choice of the bot at each step and the image into train_data. And then save the data in case of win :

#Additional data concerning the result of the game (forked initial release to add this)
def on_end(self, game_result):
print('--- on_end called ---')
print(game_result)
#We save the game if we won
if game_result == Result.Victory:
np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Frankly, I worked on this bot quite extensively in the last week and I probably tweaked quite a lot of things to optimize a bit the build order, the micro, the different units I created, and I probably forgot to mention some of those in this post. But you can find the whole code at the end of the article!

Now, let’s see a full video of BraxBot vs a very hard Zerg computer, to see how the minimap behaves :

In the last few days my bot did quite OK on the AIArena ladder and managed to squeeze a few wins here and there. I still lose a lot as the control of my stalkers and the rest of my army is not optimal. My army does not retreat when outnumbered, I keep throwing units at well defended bases and the late game is lackluster to say the least. I should probably implement a method that checks if my army supply is > or < than the supply of my opponent and go from there. Nevertheless, here is my record for the last days:

Finally let’s a full game versus one of our opponents online and see how our bot deals with other bots now !

I will stop my work on my starcraft bot here as I have multiple other projects lining up and will have less time to devote to this one. But will take it from here soon with the training of a model and the elaboration of real DNN bot !

Here is the full code so far :

#/*******************************************************
#Nom ......... : 05 Starcraft_Enhanced_Stalker_Bot_DT_LATE_Map.py
#Context ......: Advanced Protoss Stalker with colossus and DTs Micro Bot - With Map - For Training
#Role .........: Mid game push with upgrades and blink and superior Micro Skills + Late game with collos and DT
#Auteur ...... : JDO
#Version ..... : V1
#Date ........ : 10.10.2020
#Language : Python
#Version : 3.7.8
#Currently playing on AiArena
#********************************************************/
#add sentries with guardian shield ?
#Increae distance for blink distance
#bring down limit of shield for retreate so stalkers come back faster to fight
#Imports
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
import sc2
from sc2 import Race, Difficulty
from sc2.constants import *
from sc2.position import Point2, Point3
from sc2.unit import Unit
from sc2.player import Bot, Computer
from sc2.player import Human
from sc2.ids.unit_typeid import UnitTypeId
from sc2.ids.ability_id import AbilityId
from sc2.units import Units
import random
import cv2
import numpy as np
import time
#If you want to show the data (won't apply to pop up game)
HEADLESS = False
#Our bot
class BraxBot(sc2.BotAI):
#Init class
def __init__(self):
sc2.BotAI.__init__(self)
self.proxy_built = False
#on step function
async def on_step(self, iteration):
#Async actions
await self.distribute_workers()
await self.build_probes()
await self.build_pylons()
await self.build_assimilators()
await self.expand()
await self.build_production_build()
await self.warp_new_units_DT()
await self.build_units()
await self.fight()
await self.chrono_boost()
await self.morph_warpgate()
await self.warpgate_research()
await self.warp_new_units()
await self.forge_research()
await self.twilight_research()
await self.micro()
await self.build_obs()
await self.send_obs()
await self.send_dark_temp()
await self.build_colossus()
await self.robo_bay()
await self.clear_map()
await self.intel()
async def clear_map(self):
if self.supply_used > 150:
army = self.units.filter(lambda unit: unit.type_id in {UnitTypeId.COLOSSUS, UnitTypeId.STALKER, UnitTypeId.DARKTEMPLAR, UnitTypeId.ZEALOT})
ground_enemies = self.enemy_units.filter(
lambda unit: not unit.is_flying and unit.UnitTypeId not in {UnitTypeId.LARVA, UnitTypeId.EGG}
)
# we dont see anything so start to clear the map
if not ground_enemies:
for unit in army:
# clear found structures
if self.enemy_structures:
# focus down low hp structures first
in_range_structures = self.enemy_structures.in_attack_range_of(unit)
if in_range_structures:
lowest_hp = min(in_range_structures, key=lambda e: (e.health + e.shield, e.tag))
if unit.weapon_cooldown == 0:
self.do(unit.attack(lowest_hp))
else:
# dont go closer than 1 with roaches to use ranged attack
if unit.ground_range > 1:
self.do(unit.move(lowest_hp.position.towards(unit, 1 + lowest_hp.radius)))
else:
self.do(unit.move(lowest_hp.position))
else:
self.do(unit.move(self.enemy_structures.closest_to(unit)))
# check bases to find new structures
else:
self.do(unit.move(self.army_target))
return
#Build more probes
async def build_probes(self):
#For every nexus that is ready and that does not train a probe at the moment train a probe
#If we have less than 22 probes / Nexus
for nexus in self.townhalls.ready:
if self.workers.amount < self.townhalls.amount * 22 and nexus.is_idle:
if self.can_afford(UnitTypeId.PROBE):
nexus.train(UnitTypeId.PROBE)
#Build more pylons
async def build_pylons(self):
#If supply remaining is less than 6 and there is no pylon already being built, then we build a pylon near the nexus but
#We build it towards the center of the map so that the building are not getting in the way of our probes gathering minerals
if self.supply_left < 6 and self.already_pending(UnitTypeId.PYLON) == 0 and self.can_afford(UnitTypeId.PYLON) and self.supply_used < 100:
for nexus in self.townhalls.ready:
#if we have enough minerals to build a pylon
if self.can_afford(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) == 0:
await self.build(UnitTypeId.PYLON, near=nexus.position.towards(self.game_info.map_center, 7))
#If we have more than 50 supply we can create 2 pylons at once and we build them toward the center of the map
elif self.supply_used > 50 and self.supply_left < 6:
for nexus in self.townhalls.ready:
#if we have enough minerals to build a pylon
if self.can_afford(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) < 2:
await self.build(UnitTypeId.PYLON, near=nexus.position.towards(self.game_info.map_center, 20))
elif self.supply_used > 100 and self.supply_left < 8:
for nexus in self.townhalls.ready:
#if we have enough minerals to build a pylon
if self.can_afford(UnitTypeId.PYLON) and self.already_pending(UnitTypeId.PYLON) < 3:
await self.build(UnitTypeId.PYLON, near=nexus.position.towards(self.game_info.map_center, 30))
#Build gas assimilators
async def build_assimilators(self):
#We need gas to build stalkers so we first need to build an assimilator.
#Our distribute workers method will then assign workers to the gas
if self.supply_used > 15:
for nexus in self.townhalls.ready:
vgs = self.vespene_geyser.closer_than(15, nexus)
for vg in vgs:
if not self.can_afford(UnitTypeId.ASSIMILATOR):
break
worker = self.select_build_worker(vg.position)
if worker is None:
break
if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg):
if self.already_pending(UnitTypeId.ASSIMILATOR) == 0:
worker.build(UnitTypeId.ASSIMILATOR, vg)
worker.stop(queue=True)
#We build an expansation when we can afford it and we build another one when we are above 50 supply and so on
async def expand(self):
if self.townhalls.ready.amount + self.already_pending(UnitTypeId.NEXUS) < 2:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
elif self.townhalls.ready.amount + self.already_pending(UnitTypeId.NEXUS) < 3 and self.supply_used > 50 and self.supply_used < 80 and self.already_pending(UnitTypeId.NEXUS) == 0:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
elif self.townhalls.ready.amount + self.already_pending(UnitTypeId.NEXUS) < 4 and self.supply_used > 80 and self.already_pending(UnitTypeId.NEXUS) == 0:
if self.can_afford(UnitTypeId.NEXUS):
await self.expand_now()
#Build production buildings
async def build_production_build(self):
if self.structures(UnitTypeId.PYLON).ready:
pylon = self.structures(UnitTypeId.PYLON).ready.random
#If we have a gate and no CC we build a CC
if self.already_pending(UnitTypeId.GATEWAY) == 1 and not self.structures(UnitTypeId.CYBERNETICSCORE) :
if self.structures(UnitTypeId.GATEWAY).ready and self.can_afford(UnitTypeId.CYBERNETICSCORE) and self.already_pending(UnitTypeId.CYBERNETICSCORE) == 0:
await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
else:
#We build 2 gates when we can afford it
if (
self.can_afford(UnitTypeId.GATEWAY)
and self.structures(UnitTypeId.WARPGATE).amount + self.structures(UnitTypeId.GATEWAY).amount < 3
):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#We build a twilight councul when we have at least 2 gates and an expend
elif (
self.can_afford(UnitTypeId.TWILIGHTCOUNCIL)
and self.structures(UnitTypeId.WARPGATE).amount + self.structures(UnitTypeId.GATEWAY).amount > 2
and not self.structures(UnitTypeId.TWILIGHTCOUNCIL)
and self.already_pending(UnitTypeId.TWILIGHTCOUNCIL) == 0
and self.structures(UnitTypeId.NEXUS).amount > 1
):
await self.build(UnitTypeId.TWILIGHTCOUNCIL, near=pylon)
#We build a forge when we have at least 2 gates and an expend
elif (
self.can_afford(UnitTypeId.FORGE)
and self.structures(UnitTypeId.WARPGATE).amount + self.structures(UnitTypeId.GATEWAY).amount > 2
and not self.structures(UnitTypeId.FORGE)
and self.structures(UnitTypeId.CYBERNETICSCORE).ready.amount == 1
and self.already_pending(UnitTypeId.FORGE) == 0
and self.structures(UnitTypeId.NEXUS).amount > 1
):
await self.build(UnitTypeId.FORGE, near=pylon)
elif (
self.can_afford(UnitTypeId.DARKSHRINE)
and self.structures(UnitTypeId.DARKSHRINE).amount < 1
and self.structures(UnitTypeId.NEXUS).amount > 1
and self.structures(UnitTypeId.TWILIGHTCOUNCIL).amount == 1
and self.already_pending(UnitTypeId.DARKSHRINE) == 0
):
await self.build(UnitTypeId.DARKSHRINE, near=pylon)
#We build to 7 gates when we have an expend
elif (
self.can_afford(UnitTypeId.GATEWAY)
and self.structures(UnitTypeId.WARPGATE).amount + self.structures(UnitTypeId.GATEWAY).amount < 5
and self.structures(UnitTypeId.NEXUS).amount > 1
and self.structures(UnitTypeId.NEXUS).amount < 3
and self.already_pending(UnitTypeId.GATEWAY) == 0
):
await self.build(UnitTypeId.GATEWAY, near=pylon)
#We build a robotics facility when we have an expend
elif (
self.can_afford(UnitTypeId.ROBOTICSFACILITY)
and self.structures(UnitTypeId.ROBOTICSFACILITY).amount < 1
and self.structures(UnitTypeId.NEXUS).amount > 1
and self.already_pending(UnitTypeId.ROBOTICSFACILITY) == 0
):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
#We build a robotics facility when we have an expend
elif (
self.can_afford(UnitTypeId.GATEWAY)
and self.structures(UnitTypeId.WARPGATE).amount + self.structures(UnitTypeId.GATEWAY).amount < 8
and self.structures(UnitTypeId.NEXUS).amount > 2
):
await self.build(UnitTypeId.GATEWAY, near=pylon)
elif (
self.can_afford(UnitTypeId.ROBOTICSBAY)
and self.structures(UnitTypeId.ROBOTICSBAY).amount < 1
and self.structures(UnitTypeId.ROBOTICSFACILITY).amount > 0
and self.structures(UnitTypeId.NEXUS).amount > 2
):
await self.build(UnitTypeId.ROBOTICSBAY, near=pylon)
elif (
self.can_afford(UnitTypeId.ROBOTICSFACILITY)
and self.structures(UnitTypeId.ROBOTICSFACILITY).amount < 2
and self.structures(UnitTypeId.ROBOTICSBAY).amount > 0
and self.structures(UnitTypeId.NEXUS).amount > 2
):
await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
#Build Units - Zealots
async def build_units(self):
#for each gw that is not producing units
for gw in self.structures(UnitTypeId.GATEWAY).ready:
#if we can afford it and if we have enough supply left and the CC is not yet done, we produce a few zealots
if self.can_afford(UnitTypeId.ZEALOT) and not self.structures(UnitTypeId.CYBERNETICSCORE).ready and gw.is_idle and self.units(UnitTypeId.ZEALOT).amount < 2:
gw.train(UnitTypeId.ZEALOT)
#Build Units - Zealots
async def build_colossus(self):
#for each gw that is not producing units
for rf in self.structures(UnitTypeId.ROBOTICSFACILITY).ready:
#if we can afford it and if we have enough supply left and the CC is not yet done, we produce a few zealots
if self.can_afford(UnitTypeId.COLOSSUS) and self.units(UnitTypeId.ROBOTICSBAY).ready.amount > 0 and self.units(UnitTypeId.COLOSSUS).amount < 3:
rf.train(UnitTypeId.COLOSSUS)
#Build Units - Observers
async def build_obs(self):
#for each robotics facility that is not producing units
for rf in self.structures(UnitTypeId.ROBOTICSFACILITY).ready:
#if we can afford it and if we have enough supply left we produce 2 observers
if self.can_afford(UnitTypeId.OBSERVER ) and self.units(UnitTypeId.OBSERVER).amount < 2 and rf.is_idle:
#we produce a stalker
rf.train(UnitTypeId.OBSERVER)
#Method to warp units
async def warp_new_units(self):
for warpgate in self.structures(UnitTypeId.WARPGATE).ready:
#We take the abilities of the warpgate to retrieves the warping stalker ability
abilities = await self.get_available_abilities(warpgate)
#if it is not on cooldown
if AbilityId.WARPGATETRAIN_STALKER in abilities and self.units(UnitTypeId.STALKER).amount < 20:
#we sort the pylons by their distance to our warp gate
self.ordered_pylons = sorted(self.structures(UnitTypeId.PYLON).ready, key=lambda pylon: pylon.distance_to(warpgate))
#we pick the pylon the further away from the warp gate, because why not
pos = self.ordered_pylons[-1].position.random_on_distance(4)
#we select the placement of the stalker we want to warp as the position of the pylon selected
placement = await self.find_placement(AbilityId.WARPGATETRAIN_STALKER, pos, placement_step=1)
#if no placement available we return error
if placement is None:
print("can't place")
return
#else we warp our stalker !
warpgate.warp_in(UnitTypeId.STALKER, placement)
elif AbilityId.WARPGATETRAIN_STALKER in abilities and self.units(UnitTypeId.STALKER).amount < 30 and self.supply_used > 100:
#we sort the pylons by their distance to our warp gate
self.ordered_pylons = sorted(self.structures(UnitTypeId.PYLON).ready, key=lambda pylon: pylon.distance_to(warpgate))
#we pick the pylon the further away from the warp gate, because why not
pos = self.ordered_pylons[-1].position.random_on_distance(4)
#we select the placement of the stalker we want to warp as the position of the pylon selected
placement = await self.find_placement(AbilityId.WARPGATETRAIN_STALKER, pos, placement_step=1)
#if no placement available we return error
if placement is None:
print("can't place")
return
#else we warp our stalker !
warpgate.warp_in(UnitTypeId.STALKER, placement)
#Method to warp units
async def warp_new_units_DT(self):
for warpgate in self.structures(UnitTypeId.WARPGATE).ready:
#We take the abilities of the warpgate to retrieves the warping stalker ability
abilities = await self.get_available_abilities(warpgate)
#if it is not on cooldown
if AbilityId.WARPGATETRAIN_DARKTEMPLAR in abilities and self.units(UnitTypeId.DARKTEMPLAR).amount < 3:
#we sort the pylons by their distance to our warp gate
self.ordered_pylons = sorted(self.structures(UnitTypeId.PYLON).ready, key=lambda pylon: pylon.distance_to(warpgate))
#we pick the pylon the further away from the warp gate, because why not
pos = self.ordered_pylons[-1].position.random_on_distance(4)
#we select the placement of the stalker we want to warp as the position of the pylon selected
placement = await self.find_placement(AbilityId.WARPGATETRAIN_DARKTEMPLAR , pos, placement_step=1)
#if no placement available we return error
if placement is None:
print("can't place")
return
#else we warp our stalker !
warpgate.warp_in(UnitTypeId.DARKTEMPLAR , placement)
#Method for Chrono boost
async def chrono_boost(self):
#We get the list of our buildings that we want to chronoboost
if self.structures(UnitTypeId.CYBERNETICSCORE).ready:
ccore = self.structures(UnitTypeId.CYBERNETICSCORE).ready.first
if self.structures(UnitTypeId.FORGE).ready:
forge = self.structures(UnitTypeId.FORGE).ready.first
if self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready:
twilight = self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready.first
#CC is on the top of our priority list if researches the upgrade for warp gate
if self.structures(UnitTypeId.CYBERNETICSCORE).ready and not ccore.is_idle and not ccore.has_buff(BuffId.CHRONOBOOSTENERGYCOST):
for nexus in self.townhalls.ready:
if nexus.energy >= 50:
nexus(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, ccore)
#then blink in the twilight council
elif self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready and not twilight.is_idle and not twilight.has_buff(BuffId.CHRONOBOOSTENERGYCOST):
for nexus in self.townhalls.ready:
if nexus.energy >= 50:
nexus(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, twilight)
#then the forge for the upgrades
elif self.structures(UnitTypeId.FORGE).ready and not forge.is_idle and not forge.has_buff(BuffId.CHRONOBOOSTENERGYCOST):
for nexus in self.townhalls.ready:
if nexus.energy >= 50:
nexus(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, forge)
#finally the nexus if we are under 70 probes
elif self.workers.amount < 70:
for nexus in self.townhalls.ready:
if not nexus.has_buff(BuffId.CHRONOBOOSTENERGYCOST) and not nexus.is_idle and self.already_pending(UnitTypeId.CYBERNETICSCORE) < 1 and self.workers.amount < 44:
if nexus.energy >= 65:
nexus(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, nexus)
#and then the warpgates
else:
for nexus in self.townhalls.ready:
if nexus.energy >= 50:
for warpgate in self.structures(UnitTypeId.WARPGATE).ready:
if not warpgate.has_buff(BuffId.CHRONOBOOSTENERGYCOST):
if nexus.energy >= 50:
nexus(AbilityId.EFFECT_CHRONOBOOSTENERGYCOST, nexus)
#method to research warpgate, if CC ready, and can afford and not yet researched
async def warpgate_research(self):
if (
self.structures(UnitTypeId.CYBERNETICSCORE).ready
and self.can_afford(AbilityId.RESEARCH_WARPGATE)
and self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 0
):
#We research warp gate !
ccore = self.structures(UnitTypeId.CYBERNETICSCORE).ready.first
ccore.research(UpgradeId.WARPGATERESEARCH)
#method to research blink, if TC is ready, if we can affort blink and not yet researched
async def twilight_research(self):
if (
self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready
and self.can_afford(AbilityId.RESEARCH_BLINK)
and self.already_pending_upgrade(UpgradeId.BLINKTECH) == 0
):
#We research blink !
twilight = self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready.first
twilight.research(UpgradeId.BLINKTECH)
#method to research the upgrades at the forge
async def forge_research(self):
if self.structures(UnitTypeId.FORGE).ready:
#we get our forge and if we can afford and not already upgrading we cycle through the upgrades until lvl 2
forge = self.structures(UnitTypeId.FORGE).ready.first
if (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL1)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL1)
elif (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL1)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDARMORSLEVEL1) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDARMORSLEVEL1)
elif (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL2)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL2)
elif (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL2)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDARMORSLEVEL2) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDARMORSLEVEL2)
elif (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDWEAPONSLEVEL3)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDWEAPONSLEVEL3)
elif (
self.can_afford(AbilityId.FORGERESEARCH_PROTOSSGROUNDARMORLEVEL3)
and self.already_pending_upgrade(UpgradeId.PROTOSSGROUNDARMORSLEVEL3) == 0
and forge.is_idle
):
forge.research(UpgradeId.PROTOSSGROUNDARMORSLEVEL3)
async def robo_bay(self):
if (
self.structures(UnitTypeId.ROBOTICSBAY).ready
and self.can_afford(AbilityId.RESEARCH_EXTENDEDTHERMALLANCE )
and self.already_pending_upgrade(UpgradeId.EXTENDEDTHERMALLANCE ) == 0
):
#We research extended thermal !
robo = self.structures(UnitTypeId.ROBOTICSBAY).ready.first
robo.research(UpgradeId.EXTENDEDTHERMALLANCE)
#Stalker behavior
async def fight(self):
enemies = self.enemy_units.filter(lambda unit: unit.type_id not in {UnitTypeId.LARVA, UnitTypeId.EGG})
enemy_fighters = enemies.filter(lambda u: u.can_attack) + self.enemy_structures(
{UnitTypeId.BUNKER, UnitTypeId.SPINECRAWLER, UnitTypeId.PHOTONCANNON}
)
if self.units(UnitTypeId.STALKER).amount > 15:
for stalker in self.units(UnitTypeId.STALKER).ready.idle:
if enemy_fighters:
# select enemies in range
in_range_enemies = enemy_fighters.in_attack_range_of(stalker)
if in_range_enemies:
# prioritize workers
workers = in_range_enemies({UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.PROBE})
if workers:
in_range_enemies = workers
# special micro for ranged units
if stalker.ground_range > 1:
# attack if weapon not on cooldown
if stalker.weapon_cooldown == 0:
# attack enemy with lowest hp of the ones in range
lowest_hp = min(in_range_enemies, key=lambda e: (e.health + e.shield, e.tag))
self.do(stalker.attack(lowest_hp))
else:
# micro away from closest unit
# move further away if too many enemies are near
friends_in_range = self.units(UnitTypeId.STALKER).in_attack_range_of(stalker)
closest_enemy = in_range_enemies.closest_to(stalker)
distance = stalker.ground_range + stalker.radius + closest_enemy.radius
if (
len(friends_in_range) <= len(in_range_enemies)
and closest_enemy.ground_range <= stalker.ground_range
):
distance += 1
else:
# if more than 5 units friends are close, use distance one shorter than range
# to let other friendly units get close enough as well and not block each other
if len(self.units(UnitTypeId.STALKER).closer_than(7, stalker.position)) >= 5:
distance -= -1
self.do(stalker.move(closest_enemy.position.towards(stalker, distance)))
else:
# target fire with melee units
lowest_hp = min(in_range_enemies, key=lambda e: (e.health + e.shield, e.tag))
self.do(stalker.attack(lowest_hp))
else:
# no unit in range, go to closest
self.do(stalker.move(enemy_fighters.closest_to(stalker)))
# no dangerous enemy at all, attack closest anything
else:
stalker.attack(self.enemy_start_locations[0])
elif self.units(UnitTypeId.STALKER).amount > 0 and self.units(UnitTypeId.STALKER).amount < 15:
for stalker in self.units(UnitTypeId.STALKER).ready.idle:
if enemy_fighters:
# select enemies in range
in_range_enemies = enemy_fighters.in_attack_range_of(stalker)
if in_range_enemies:
# prioritize workers
workers = in_range_enemies({UnitTypeId.DRONE, UnitTypeId.SCV, UnitTypeId.PROBE})
if workers:
in_range_enemies = workers
# special micro for ranged units
if stalker.ground_range > 1:
# attack if weapon not on cooldown
if stalker.weapon_cooldown == 0:
# attack enemy with lowest hp of the ones in range
lowest_hp = min(in_range_enemies, key=lambda e: (e.health + e.shield, e.tag))
self.do(stalker.attack(lowest_hp))
else:
# micro away from closest unit
# move further away if too many enemies are near
friends_in_range = self.units(UnitTypeId.STALKER).in_attack_range_of(stalker)
closest_enemy = in_range_enemies.closest_to(stalker)
distance = stalker.ground_range + stalker.radius + closest_enemy.radius
if (
len(friends_in_range) <= len(in_range_enemies)
and closest_enemy.ground_range <= stalker.ground_range
):
distance += 1
else:
# if more than 5 units friends are close, use distance one shorter than range
# to let other friendly units get close enough as well and not block each other
if len(self.units(UnitTypeId.STALKER).closer_than(7, stalker.position)) >= 5:
distance -= -1
self.do(stalker.move(closest_enemy.position.towards(stalker, distance)))
else:
# target fire with melee units
lowest_hp = min(in_range_enemies, key=lambda e: (e.health + e.shield, e.tag))
self.do(stalker.attack(lowest_hp))
else:
# no unit in range, go to closest
self.do(stalker.move(enemy_fighters.closest_to(stalker)))
# no dangerous enemy at all, attack closest anything
else:
#our defense/early harass mechanic with our zealots
for zealot in self.units(UnitTypeId.ZEALOT).ready.idle:
if enemy_fighters:
self.do(zealot.attack(random.choice(enemy_fighters)))
else:
self.do(zealot.attack(random.choice(self.ordered_expansions[0:3])))
for colo in self.units(UnitTypeId.COLOSSUS).ready.idle:
if enemy_fighters:
self.do(colo.attack(random.choice(enemy_fighters)))
else:
self.do(colo.attack(random.choice(self.ordered_expansions[0:4])))
async def send_dark_temp(self):
enemies = self.enemy_units.filter(lambda unit: unit.type_id not in {UnitTypeId.LARVA, UnitTypeId.EGG})
enemy_fighters = enemies.filter(lambda u: u.can_attack) + self.enemy_structures(
{UnitTypeId.BUNKER, UnitTypeId.SPINECRAWLER, UnitTypeId.PHOTONCANNON}
)
for dt in self.units(UnitTypeId.DARKTEMPLAR).ready.idle:
if enemy_fighters:
self.do(dt.attack(random.choice(enemy_fighters)))
else:
self.do(dt.attack(random.choice(self.ordered_expansions[0:3])))
#Method to morph our gateways into warpgates, if gateway is ready and idle, and we have done the upgrade then morph
async def morph_warpgate(self):
for gateway in self.structures(UnitTypeId.GATEWAY).ready.idle:
if self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 1:
gateway(AbilityId.MORPH_WARPGATE)
#Our method to micro and to blink
async def micro(self):
home_location = self.start_location
enemies: Units = self.enemy_units | self.enemy_structures
enemies2 = self.enemy_units.filter(lambda unit: unit.type_id not in {UnitTypeId.DRONE,UnitTypeId.SCV})
enemies_can_attack: Units = enemies2.filter(lambda unit: unit.can_attack_ground)
for stalker in self.units(UnitTypeId.STALKER).ready:
escape_location = stalker.position.towards(home_location, 6)
enemyThreatsClose: Units = enemies_can_attack.filter(lambda unit: unit.distance_to(stalker) < 15) # Threats that can attack the stalker
if stalker.shield < 4 and enemyThreatsClose:
abilities = await self.get_available_abilities(stalker)
if AbilityId.EFFECT_BLINK_STALKER in abilities:
#await self.order(stalker, EFFECT_BLINK_STALKER, escape_location)
stalker(AbilityId.EFFECT_BLINK_STALKER, escape_location)
continue
else:
retreatPoints: Set[Point2] = self.around8(stalker.position, distance=2) | self.around8(stalker.position, distance=4)
# Filter points that are pathable
retreatPoints: Set[Point2] = {x for x in retreatPoints if self.in_pathing_grid(x)}
if retreatPoints:
closestEnemy: Unit = enemyThreatsClose.closest_to(stalker)
retreatPoint: Unit = closestEnemy.position.furthest(retreatPoints)
stalker.move(retreatPoint)
continue # Continue for loop, dont execute any of the following
#Method to send our obs scouting
async def send_obs(self):
#We retrieve the list of possible extensions
self.ordered_expansions = None
self.ordered_expansions = sorted(
self.expansion_locations.keys(), key=lambda expansion: expansion.distance_to(self.enemy_start_locations[0])
)
#We send the obs in each, initially I limited at 4 but then in some games I could not find a hidden expo..
for obs in self.units(UnitTypeId.OBSERVER).ready:
if obs.is_idle:
location=random.choice(self.ordered_expansions[0:]).position.random_on_distance(10)
self.do(obs.move(location))
# Stolen and modified from position.py
def around8(self, position, distance=1) -> Set[Point2]:
p = position
d = distance
return self.around4(position, distance) | {
Point2((p.x - d, p.y - d)),
Point2((p.x - d, p.y + d)),
Point2((p.x + d, p.y - d)),
Point2((p.x + d, p.y + d)),
}
# Stolen and modified from position.py
def around4(self, position, distance=1) -> Set[Point2]:
p = position
d = distance
return {Point2((p.x - d, p.y)), Point2((p.x + d, p.y)), Point2((p.x, p.y - d)), Point2((p.x, p.y + d))}
#Draw all info on map
async def intel(self):
game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
draw_dict = {
NEXUS: [(0, 255, 0)],
PYLON: [(10, 255, 0)],
DARKSHRINE: [(20, 255, 0)],
ASSIMILATOR: [(30, 255, 0)],
GATEWAY: [(40, 255, 0)],
CYBERNETICSCORE: [(50, 255, 0)],
STARGATE: [(60, 255, 0)],
ROBOTICSFACILITY: [(70, 255, 0)],
WARPGATE: [(80, 255, 0)],
FORGE: [(90, 255, 0)],
ROBOTICSBAY: [(100, 255, 0)],
TWILIGHTCOUNCIL: [(110, 255, 0)],
COLOSSUS: [(0, 255, 10)],
ZEALOT: [(0, 255, 20)],
STALKER: [(0, 255, 30)],
OBSERVER: [(0, 255, 40)],
DARKTEMPLAR: [(0, 255, 50)],
PROBE: [(0, 255, 60)],
VOIDRAY: [(0, 255, 70)],
HATCHERY: [(255, 0, 5)],
EXTRACTOR: [(255, 0, 6)],
SPAWNINGPOOL: [(255, 0, 7)],
EVOLUTIONCHAMBER: [(255, 0, 8)],
HYDRALISKDEN: [(255, 0, 9)],
SPIRE: [(255, 0, 10)],
ULTRALISKCAVERN: [(255, 0, 11)],
INFESTATIONPIT: [(255, 0, 12)],
NYDUSNETWORK: [(255, 0, 13)],
BANELINGNEST: [(255, 0, 14)],
ROACHWARREN: [(255, 0, 15)],
SPINECRAWLER: [(255, 0, 16)],
SPORECRAWLER: [(255, 0, 17)],
LAIR: [(255, 0, 18)],
HIVE: [(255, 0, 19)],
GREATERSPIRE: [(255, 0, 20)],
DRONE: [(255, 0, 21)],
ZERGLING: [(255, 0, 22)],
OVERLORD: [(255, 0, 23)],
HYDRALISK: [(255, 0, 24)],
MUTALISK: [(255, 0, 25)],
ULTRALISK: [(255, 0, 26)],
ROACH: [(255, 0, 27)],
INFESTOR: [(255, 0, 28)],
CORRUPTOR: [(255, 0, 29)],
BROODLORDCOCOON: [(255, 0, 30)],
BROODLORD: [(255, 0, 31)],
QUEEN: [(255, 0, 32)],
OVERSEER: [(255, 0, 33)]
}
army = self.units
ground_enemies = self.enemy_units
for unit_type in draw_dict:
for unit in self.units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius), draw_dict[unit_type][0], -1)
for structure_type in draw_dict:
for structure in self.structures(structure_type).ready:
pos = structure.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(structure.radius), draw_dict[structure_type][0], -1)
if self.enemy_units:
for unit_type in draw_dict:
for unit in self.enemy_units(unit_type).ready:
pos = unit.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(unit.radius), draw_dict[unit_type][0], -1)
if self.enemy_structures:
for structure_type in draw_dict:
for structure in self.enemy_structures(structure_type).ready:
pos = structure.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(structure.radius), draw_dict[structure_type][0], -1)
for mineral in self.mineral_field:
pos = mineral.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(mineral.radius),(236, 248, 248) , -1)
for vespene in self.vespene_geyser:
pos = vespene.position
cv2.circle(game_data, (int(pos[0]), int(pos[1])), int(vespene.radius),(180, 227, 204) , -1)
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)
def main():
sc2.run_game(
sc2.maps.get("Abyssal Reef LE"),
[Bot(Race.Protoss, BraxBot()), Computer(Race.Zerg, Difficulty.VeryHard)],
realtime=True,
save_replay_as="Example.SC2Replay",
)
#def main():
# sc2.run_game(
# sc2.maps.get("Abyssal Reef LE"),
# [Human(Race.Terran), Bot(Race.Protoss, BraxBot())],
# realtime=True,
# save_replay_as="Example.SC2Replay",
# )
if __name__ == "__main__":
main()
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Brax

Dude in his 30s starting his digital notepad