@@ -126,15 +126,24 @@ 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) """
134- self .cached_main_base_ramp = min (
135- (ramp for ramp in self .game_info .map_ramps if len (ramp .upper ) in {2 , 5 }),
136- key = lambda r : self .start_location .distance_to (r .top_center ),
137- )
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).
136+ try :
137+ self .cached_main_base_ramp = min (
138+ (ramp for ramp in self .game_info .map_ramps if len (ramp .upper ) in {2 , 5 }),
139+ key = lambda r : self .start_location .distance_to (r .top_center ),
140+ )
141+ except ValueError :
142+ # Hardcoded hotfix for Honorgrounds LE map, as that map has a large main base ramp with inbase natural
143+ self .cached_main_base_ramp = min (
144+ (ramp for ramp in self .game_info .map_ramps if len (ramp .upper ) in {4 , 9 }),
145+ key = lambda r : self .start_location .distance_to (r .top_center ),
146+ )
138147 return self .cached_main_base_ramp
139148
140149 @property_cache_forever
@@ -148,64 +157,65 @@ def expansion_locations(self) -> Dict[Point2, Units]:
148157 # any resource in a group is closer than 6 to any resource of another group
149158
150159 # 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
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
209219
210220 def _correct_zerg_supply (self ):
211221 """ The client incorrectly rounds zerg supply down instead of up (see
0 commit comments