@@ -126,11 +126,13 @@ def known_enemy_structures(self) -> Units:
126126
127127 @property
128128 def main_base_ramp (self ) -> "Ramp" :
129- """ Returns the Ramp instance of the closest main-ramp to start location. Look in game_info.py for more information """
129+ """ Returns the Ramp instance of the closest main-ramp to start location.
130+ Look in game_info.py for more information """
130131 if hasattr (self , "cached_main_base_ramp" ):
131132 return self .cached_main_base_ramp
132- """ The reason for len(ramp.upper) in {2, 5} is:
133- ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp. The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position) """
133+ # The reason for len(ramp.upper) in {2, 5} is:
134+ # ParaSite map has 5 upper points, and most other maps have 2 upper points at the main ramp.
135+ # The map Acolyte has 4 upper points at the wrong ramp (which is closest to the start position).
134136 self .cached_main_base_ramp = min (
135137 (ramp for ramp in self .game_info .map_ramps if len (ramp .upper ) in {2 , 5 }),
136138 key = lambda r : self .start_location .distance_to (r .top_center ),
@@ -148,64 +150,65 @@ def expansion_locations(self) -> Dict[Point2, Units]:
148150 # any resource in a group is closer than 6 to any resource of another group
149151
150152 # Distance we group resources by
151- RESOURCE_SPREAD_THRESHOLD = 8.5
152- minerals = self .state .mineral_field
153- geysers = self .state .vespene_geyser
154- all_resources = minerals | geysers
155- # Presort resources to get faster clustering
156- all_resources .sort (key = lambda resource : (resource .position .x , resource .position .y ))
157- # Create a group for every resource
158- resource_groups = [[resource ] for resource in all_resources ]
159- # Loop the merging process as long as we change something
160- found_something = True
161- while found_something :
162- found_something = False
163- # Check every combination of two groups
164- for group_a , group_b in itertools .combinations (resource_groups , 2 ):
165- # Check if any pair of resource of these groups is closer than threshold together
166- if any (
167- resource_a .distance_to (resource_b ) <= RESOURCE_SPREAD_THRESHOLD
168- for resource_a , resource_b in itertools .product (group_a , group_b )
169- ):
170- # Remove the single groups and add the merged group
171- resource_groups .remove (group_a )
172- resource_groups .remove (group_b )
173- resource_groups .append (group_a + group_b )
174- found_something = True
175- break
176- # Distance offsets we apply to center of each resource group to find expansion position
177- offset_range = 7
178- offsets = [
179- (x , y )
180- for x in range (- offset_range , offset_range + 1 )
181- for y in range (- offset_range , offset_range + 1 )
182- if 4 <= math .hypot (x , y ) <= 7
183- ]
184- # Dict we want to return
185- centers = {}
186- # For every resource group:
187- for resources in resource_groups :
188- # Possible expansion points
189- amount = len (resources )
190- # Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
191- # coordinates because bases have size 5.
192- center_x = round (sum (resource .position .x for resource in resources ) / amount ) + 0.5
193- center_y = round (sum (resource .position .y for resource in resources ) / amount ) + 0.5
194- possible_points = (Point2 ((offset [0 ] + center_x , offset [1 ] + center_y )) for offset in offsets )
195- # Filter out points that are too near
196- possible_points = (
197- point
198- for point in possible_points
199- # Check if point can be built on
200- if self ._game_info .placement_grid [point .rounded ] != 0
201- # Check if all resources have enough space to point
202- and all (point .distance_to (resource ) >= (7 if resource in geysers else 6 ) for resource in resources )
203- )
204- # Choose best fitting point
205- # TODO can we improve this by calculating the distance only one time?
206- result = min (possible_points , key = lambda point : sum (point .distance_to (resource ) for resource in resources ))
207- centers [result ] = resources
208- return centers
153+ from .helpers .devtools import time_this
154+
155+ with time_this ("expo locations" ):
156+ RESOURCE_SPREAD_THRESHOLD = 8.5
157+ minerals = self .state .mineral_field
158+ geysers = self .state .vespene_geyser
159+ all_resources = minerals | geysers
160+ # Presort resources to get faster clustering
161+ # all_resources.sort(key=lambda resource: resource.position.x ** 2 + resource.position.y ** 2)
162+ all_resources .sort (key = lambda resource : resource .position .x ** 2 + resource .position .y ** 2 )
163+ # Create a group for every resource
164+ resource_groups = [[resource ] for resource in all_resources ]
165+ # Loop the merging process as long as we change something
166+ found_something = True
167+ while found_something :
168+ found_something = False
169+ # Check every combination of two groups
170+ for group_a , group_b in itertools .combinations (resource_groups , 2 ):
171+ # Check if any pair of resource of these groups is closer than threshold together
172+ if any (
173+ resource_a .distance_to (resource_b ) <= RESOURCE_SPREAD_THRESHOLD
174+ for resource_a , resource_b in itertools .product (group_a , group_b )
175+ ):
176+ # Remove the single groups and add the merged group
177+ resource_groups .remove (group_a )
178+ resource_groups .remove (group_b )
179+ resource_groups .append (group_a + group_b )
180+ found_something = True
181+ break
182+ # Distance offsets we apply to center of each resource group to find expansion position
183+ offset_range = 7
184+ offsets = [(x , y ) for x , y in itertools .product (range (- offset_range , offset_range + 1 ), repeat = 2 )]
185+ # Dict we want to return
186+ centers = {}
187+ # For every resource group:
188+ for resources in resource_groups :
189+ # Possible expansion points
190+ amount = len (resources )
191+ # Calculate center, round and add 0.5 because expansion location will have (x.5, y.5)
192+ # coordinates because bases have size 5.
193+ center_x = round (sum (resource .position .x for resource in resources ) / amount ) + 0.5
194+ center_y = round (sum (resource .position .y for resource in resources ) / amount ) + 0.5
195+ possible_points = (Point2 ((offset [0 ] + center_x , offset [1 ] + center_y )) for offset in offsets )
196+ # Filter out points that are too near
197+ possible_points = (
198+ point
199+ for point in possible_points
200+ # Check if point can be built on
201+ if self ._game_info .placement_grid [point .rounded ] == 1
202+ # Check if all resources have enough space to point
203+ and all (point .distance_to (resource ) > (7 if resource in geysers else 6 ) for resource in resources )
204+ )
205+ # Choose best fitting point
206+ # TODO can we improve this by calculating the distance only one time?
207+ result = min (
208+ possible_points , key = lambda point : sum (point .distance_to (resource ) for resource in resources )
209+ )
210+ centers [result ] = resources
211+ return centers
209212
210213 def _correct_zerg_supply (self ):
211214 """ The client incorrectly rounds zerg supply down instead of up (see
0 commit comments