Skip to content

Latest commit

 

History

History
227 lines (195 loc) · 10.3 KB

README.md

File metadata and controls

227 lines (195 loc) · 10.3 KB

queens-sc2

queens-sc2 is a small customizable library to aid development of sc2 zerg bots developed with python-sc2. A challenge when developing zerg bots is effective queen management since queens have various roles including injecting, creep, defence and nydusing. queens-sc2 was created to allow zerg authors to rapidly develop a bot without being encumbered by queen management. Using policies that can be updated at any time queens-sc2 provides a lot of flexibility, whether that would be aggressive nydus play, defensive queens or a mass creep style

sharpy-sc2 support

Thanks to lladdy, queens-sc2 can now also be integrated with sharpy-sc2. See here for documentation and example code.

Otherwise the rest of this readme assumes integration with python-sc2

Setting up queens-sc2 to work with python-sc2

Prerequisites

It is expected the user has already installed python-sc2, queens-sc2 also relies on numpy and scipy (any modern version is fine).

Getting started

Clone or download this repository and put the queens_sc2 directory in your bot folder like so:

MyBot
└───queens_sc2
│   └───queens_sc2 library files
└───your bot files and directories

Alternatively feel free to download QueenBot from the AI Arena ladder which always contains the most up to date version of queens-sc2.

Example bot file using python-sc2

Out of the box, the library will run without a policy but remember you have to build the queens yourself:

from sc2 import BotAI
from queens_sc2.queens import Queens

class ZergBot(BotAI):
    queens: Queens
    
    async def on_start(self) -> None:
        self.queens = Queens(self)
        
    async def on_unit_destroyed(self, unit_tag: int):
        # checks if unit is a queen or th, library then handles appropriately
        self.queens.remove_unit(unit_tag)
        
    async def on_step(self, iteration: int) -> None:
        # call the queen library to handle our queen_control
        await self.queens.manage_queens(iteration)
        
        # can optionally pass in a custom selection of queen_control, ie:
        # queen_control: Units = self.units.tags_in(self.sc2_queen_tags)
        # await self.queen_control.manage_queens(iteration, queen_control)
        # if not the library will manage all queen_control automatically
        
        
        # the rest of my awesome bot ...

Queen policy

To get the most out of this library, a custom queen policy can be passed to the library with the following options:

queen_policy: Dict = {
  "creep_queens": {
      "active": bool,
      "max": int,
      "priority": Union[bool, int],
      "defend_against_air": bool,
      "defend_against_ground": bool,
      "distance_between_existing_tumors": int, # how far an existing tumor can spread to
      "distance_between_queen_tumors": int, # when deciding to lay a tumor, queen should leave this much distance between existing tumors
      "min_distance_between_existing_tumors": int, # min distance a tumor is allowed to spread to
      "should_tumors_block_expansions": bool,
      # If using Map Analyzer, a list of start and end goals can be passed in for creep targets, creep will then follow these paths
      "creep_targets": Union[List[Point2], List[Tuple[Point2, Point2]]], # library will cycle through these locations
      "spread_style": str, # "targeted" is default, or "random".
      "rally_point": Point2,
      "first_tumor_position": Optional[Point2],
      "prioritize_creep": Callable, # prioritize over defending bases if energy is available?
      "pass_own_threats": bool, # if set to True, library wont calculate enemy near bases, you should pass air and ground threats to manage_queens() method
      "priority_defence_list": Set[UnitID] # queen_control will prioritise defending these unit types over all other jobs
  },
  "creep_dropperlord_queens": {
      "active": bool,
      "max": int, # only supports 1 queen / 1 dropperlord for now
      "priority": Union[bool, int],
      "defend_against_air": bool,
      "defend_against_ground": bool,
      "attack_condition": Callable, # only if you intend for defend queen_control to turn offensive
      "attack_target": Point2, # used by offensive defence queen_control, otherwise not required
      "rally_point": Point2,
      "pass_own_threats": bool,
      "priority_defence_list": Set[UnitID],
      "target_expansions": List[Point2]
  },
  "defence_queens": {
      "active": bool,
      "max": int,
      "priority": Union[bool, int],
      "defend_against_air": bool,
      "defend_against_ground": bool,
      "attack_condition": Callable, # only if you intend for defend queen_control to turn offensive
      "attack_target": Point2, # used by offensive defence queen_control, otherwise not required
      "rally_point": Point2,
      "pass_own_threats": bool,
      "priority_defence_list": Set[UnitID]
  },
  "inject_queens": {
      "active": bool,
      "max": int,
      "priority": Union[bool, int],
      "defend_against_air": bool,
      "defend_against_ground": bool,
      "pass_own_threats": bool,
      "priority_defence_list": Set[UnitID]
    },
    # NOTE: Nydus Queens only become active when a Canal is placed on the map, so assign Nydus Queens to another role then set that role in `steal_from`.
  "nydus_queens": {
      "active": bool,
      "max": int,
      "priority": Union[bool, int],
      "defend_against_air": bool,
      "defend_against_ground": bool,
      "pass_own_threats": bool,
      "priority_defence_list": Set[UnitID],
      "attack_target": Point2,
      "nydus_move_function": Optional[Callable], # completely optional, nydus will still work without this
      "nydus_target": Point2, # not the nydus canal itself, but the target area we want to attack once out of the canal
      "steal_from": Set[QueenRoles], # found in `queens_sc2.consts`, should contain any of: QueenRoles.Creep, QueenRoles.Defence, QueenRoles.Inject
    },
}

However the library has sane defaults for missing values, this is a valid policy for example:

async def on_start(self) -> None:
  early_game_queen_policy: Dict = {
    "creep_queens": {
        "active": True,
        "priority": True,
        "max": 4,
        "defend_against_ground": True,
    },
    "inject_queens": {"active": True, "priority": False, "max": 2},
  }
  
  self.queens = Queens(self, early_game_queen_policy)

You can pass new policies on the fly with the set_new_policy method:

mid_game_queen_policy: Dict = {
    "creep_queens": {
        "max": 2,
        "priority": True,
        "defend_against_ground": False,
        "creep_style": "random",
    },
    "defence_queens": {
        "attack_condition": lambda: self.units(UnitID.QUEEN).amount > 30,
    },
    "inject_queens": {"active": False, "max": 0},
}
self.queens.set_new_policy(queen_policy=mid_game_queen_policy, reset_roles=True)

Attack target for offensive defence queens can be updated:

self.queens.update_attack_target(self.enemy_start_locations[0])

Creep targets can also be updated with a new List of locations. (By default this is set to all expansion locations) Or if using Map Analyzer you can pass in a List of Tuple's, where each Tuple contains a starting Point2 and target Point2, queens-sc2 will then try to creep along the ground path.

# path should ideally contain no creep points
self.queens.update_creep_targets(path_to_third_base)

If using nyduses, make sure the nydus target is updated, this is not where the Nydus should be placed, rather the focal attack point from the Nydus itself:

self.queens.update_nydus_target(self.enemy_start_locations[0])

Creep Dropperlord Queens

queens-sc2 now supports a dropperlord and queen combo. The dropperlord will find a creep target, drop the queen off and poop creep while the queen lays a tumor. After which the queens is picked back up and flown to the next creep location.

** Only supports a single dropperlord and queen combo **

queens-sc2 will steal a creep queen so ensure your policy reflects this. There are already sane defaults for the creep dropperlord but refer to policy example above. queens-sc2 will NOT morph the dropperlord, and you should pass in a Set of dropperlord tags via the main manage_queens method, for example:

await self.queens.manage_queens(
    iteration, 
    creep_queen_dropperlord_tags=self.my_freely_available_dropperlord_tags
)

Despite only a single dropperlord being supported currently, a Set of unit tags is required, in case multiple dropperlords are supported in the future.

SC2 Map Analyzer support

queens-sc2 comes with completely optional support for SC2 Map Analyzer, currently this allows for improved creep spread and better Queen control. A priority avoidance grid may be passed, with threats prepopulated on the grid so that Queens avoid areas.

Example setup with MA (please follow instructions on the MA repo if needed):

    from sc2 import BotAI
    from MapAnalyzer import MapData
    from queens_sc2.queens import Queens
    
    class ZergBot(BotAI):
        async def on_start(self) -> None:
            self.map_data = MapData(self)  # where self is your BotAI object from python-sc2
            self.queens = Queens(
                self, queen_policy=self.my_policy, map_data=self.map_data
            )
            
        async def on_step(self, iteration: int) -> None:
            avoidance_grid: np.ndarray = self.map_data.get_pyastar_grid()
            # add cost to avoidance_grid, for example positions of nukes / biles / storms etc
            ground_grid: np.ndarray = self.map_data.get_pyastar_grid()
            # you may want to add cost etc depending on your bot, 
            
            # depending on usecase it may not need a fresh grid every step
            await self.queens.manage_queens(iteration, avoidance_grid=avoidance_grid, grid=ground_grid)

I only want creep spread

Check the example in creep_example.py which shows how to set a creep policy and manage separate groups of queens.

Contributing

Pull requests are welcome, please submit an issue for feature requests or bug reports.