55from collections import Counter
66from typing import Any , Dict , List , Optional , Set , Tuple , Union # mypy type checking
77
8- from s2clientprotocol import common_pb2 as common_pb
9-
108from .cache import property_cache_forever , property_cache_once_per_frame
119from .data import ActionResult , Alert , Race , Result , Target , race_gas , race_townhalls , race_worker
1210from .game_data import AbilityData , GameData
@@ -130,7 +128,7 @@ def main_base_ramp(self) -> "Ramp":
130128 Look in game_info.py for more information """
131129 if hasattr (self , "cached_main_base_ramp" ):
132130 return self .cached_main_base_ramp
133- # The reason for len(ramp.upper) in {2, 5} is:
131+ # The reason for len(ramp.upper) in {2, 5} is:
134132 # ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp.
135133 # The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position).
136134 try :
@@ -157,65 +155,58 @@ def expansion_locations(self) -> Dict[Point2, Units]:
157155 # any resource in a group is closer than 6 to any resource of another group
158156
159157 # Distance we group resources by
160- from .helpers .devtools import time_this
161-
162- with time_this ("expo locations" ):
163- RESOURCE_SPREAD_THRESHOLD = 8.5
164- minerals = self .state .mineral_field
165- geysers = self .state .vespene_geyser
166- all_resources = minerals | geysers
167- # Presort resources to get faster clustering
168- # all_resources.sort(key=lambda resource: resource.position.x ** 2 + resource.position.y ** 2)
169- all_resources .sort (key = lambda resource : resource .position .x ** 2 + resource .position .y ** 2 )
170- # Create a group for every resource
171- resource_groups = [[resource ] for resource in all_resources ]
172- # Loop the merging process as long as we change something
173- found_something = True
174- while found_something :
175- found_something = False
176- # Check every combination of two groups
177- for group_a , group_b in itertools .combinations (resource_groups , 2 ):
178- # Check if any pair of resource of these groups is closer than threshold together
179- if any (
180- resource_a .distance_to (resource_b ) <= RESOURCE_SPREAD_THRESHOLD
181- for resource_a , resource_b in itertools .product (group_a , group_b )
182- ):
183- # Remove the single groups and add the merged group
184- resource_groups .remove (group_a )
185- resource_groups .remove (group_b )
186- resource_groups .append (group_a + group_b )
187- found_something = True
188- break
189- # Distance offsets we apply to center of each resource group to find expansion position
190- offset_range = 7
191- offsets = [(x , y ) for x , y in itertools .product (range (- offset_range , offset_range + 1 ), repeat = 2 )]
192- # Dict we want to return
193- centers = {}
194- # For every resource group:
195- for resources in resource_groups :
196- # Possible expansion points
197- amount = len (resources )
198- # Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
199- # coordinates because bases have size 5.
200- center_x = round (sum (resource .position .x for resource in resources ) / amount ) + 0.5
201- center_y = round (sum (resource .position .y for resource in resources ) / amount ) + 0.5
202- possible_points = (Point2 ((offset [0 ] + center_x , offset [1 ] + center_y )) for offset in offsets )
203- # Filter out points that are too near
204- possible_points = (
205- point
206- for point in possible_points
207- # Check if point can be built on
208- if self ._game_info .placement_grid [point .rounded ] == 1
209- # Check if all resources have enough space to point
210- and all (point .distance_to (resource ) > (7 if resource in geysers else 6 ) for resource in resources )
211- )
212- # Choose best fitting point
213- # TODO can we improve this by calculating the distance only one time?
214- result = min (
215- possible_points , key = lambda point : sum (point .distance_to (resource ) for resource in resources )
216- )
217- centers [result ] = resources
218- return centers
158+ RESOURCE_SPREAD_THRESHOLD = 8.5
159+ geysers = self .state .vespene_geyser
160+ # Create a group for every resource
161+ resource_groups = [[resource ] for resource in self .state .resources ]
162+ # Loop the merging process as long as we change something
163+ found_something = True
164+ while found_something :
165+ found_something = False
166+ # Check every combination of two groups
167+ for group_a , group_b in itertools .combinations (resource_groups , 2 ):
168+ # Check if any pair of resource of these groups is closer than threshold together
169+ if any (
170+ resource_a .distance_to (resource_b ) <= RESOURCE_SPREAD_THRESHOLD
171+ for resource_a , resource_b in itertools .product (group_a , group_b )
172+ ):
173+ # Remove the single groups and add the merged group
174+ resource_groups .remove (group_a )
175+ resource_groups .remove (group_b )
176+ resource_groups .append (group_a + group_b )
177+ found_something = True
178+ break
179+ # Distance offsets we apply to center of each resource group to find expansion position
180+ offset_range = 7
181+ offsets = [
182+ (x , y )
183+ for x , y in itertools .product (range (- offset_range , offset_range + 1 ), repeat = 2 )
184+ if math .hypot (x , y ) <= 8
185+ ]
186+ # Dict we want to return
187+ centers = {}
188+ # For every resource group:
189+ for resources in resource_groups :
190+ # Possible expansion points
191+ amount = len (resources )
192+ # Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
193+ # coordinates because bases have size 5.
194+ center_x = int (sum (resource .position .x for resource in resources ) / amount ) + 0.5
195+ center_y = int (sum (resource .position .y for resource in resources ) / amount ) + 0.5
196+ possible_points = (Point2 ((offset [0 ] + center_x , offset [1 ] + center_y )) for offset in offsets )
197+ # Filter out points that are too near
198+ possible_points = (
199+ point
200+ for point in possible_points
201+ # Check if point can be built on
202+ if self ._game_info .placement_grid [point .rounded ] == 1
203+ # Check if all resources have enough space to point
204+ and all (point .distance_to (resource ) > (7 if resource in geysers else 6 ) for resource in resources )
205+ )
206+ # Choose best fitting point
207+ result = min (possible_points , key = lambda point : sum (point .distance_to (resource ) for resource in resources ))
208+ centers [result ] = resources
209+ return centers
219210
220211 def _correct_zerg_supply (self ):
221212 """ The client incorrectly rounds zerg supply down instead of up (see
0 commit comments