@@ -133,7 +133,7 @@ def main_base_ramp(self) -> "Ramp":
133133 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) """
134134 self .cached_main_base_ramp = min (
135135 (ramp for ramp in self .game_info .map_ramps if len (ramp .upper ) in {2 , 5 }),
136- key = lambda r : self .start_location ._distance_squared (r .top_center ),
136+ key = lambda r : self .start_location .distance_to (r .top_center ),
137137 )
138138 return self .cached_main_base_ramp
139139
@@ -148,7 +148,7 @@ def expansion_locations(self) -> Dict[Point2, Units]:
148148 # any resource in a group is closer than 6 to any resource of another group
149149
150150 # Distance we group resources by
151- RESOURCE_SPREAD_THRESHOLD = 36
151+ RESOURCE_SPREAD_THRESHOLD = 8.5
152152 minerals = self .state .mineral_field
153153 geysers = self .state .vespene_geyser
154154 all_resources = minerals | geysers
@@ -164,7 +164,7 @@ def expansion_locations(self) -> Dict[Point2, Units]:
164164 for group_a , group_b in itertools .combinations (resource_groups , 2 ):
165165 # Check if any pair of resource of these groups is closer than threshold together
166166 if any (
167- resource_a .position . _distance_squared (resource_b . position ) <= RESOURCE_SPREAD_THRESHOLD
167+ resource_a .distance_to (resource_b ) <= RESOURCE_SPREAD_THRESHOLD
168168 for resource_a , resource_b in itertools .product (group_a , group_b )
169169 ):
170170 # Remove the single groups and add the merged group
@@ -179,7 +179,7 @@ def expansion_locations(self) -> Dict[Point2, Units]:
179179 (x , y )
180180 for x in range (- offset_range , offset_range + 1 )
181181 for y in range (- offset_range , offset_range + 1 )
182- if 49 >= x ** 2 + y ** 2 >= 16
182+ if 4 <= math . hypot ( x , y ) <= 7
183183 ]
184184 # Dict we want to return
185185 centers = {}
@@ -199,17 +199,11 @@ def expansion_locations(self) -> Dict[Point2, Units]:
199199 # Check if point can be built on
200200 if self ._game_info .placement_grid [point .rounded ] != 0
201201 # Check if all resources have enough space to point
202- and all (
203- point ._distance_squared (resource .position ) >= (49 if resource in geysers else 36 )
204- for resource in resources
205- )
202+ and all (point .distance_to (resource ) >= (7 if resource in geysers else 6 ) for resource in resources )
206203 )
207204 # Choose best fitting point
208205 # TODO can we improve this by calculating the distance only one time?
209- result = min (
210- possible_points ,
211- key = lambda point : sum (point ._distance_squared (resource .position ) for resource in resources ),
212- )
206+ result = min (possible_points , key = lambda point : sum (point .distance_to (resource ) for resource in resources ))
213207 centers [result ] = resources
214208 return centers
215209
@@ -271,7 +265,7 @@ async def get_next_expansion(self) -> Optional[Point2]:
271265 for el in self .expansion_locations :
272266
273267 def is_near_to_expansion (t ):
274- return t .position . _distance_squared (el ) < self .EXPANSION_GAP_THRESHOLD ** 2
268+ return t .distance_to (el ) < self .EXPANSION_GAP_THRESHOLD
275269
276270 if any (map (is_near_to_expansion , self .townhalls )):
277271 # already taken
@@ -288,16 +282,27 @@ def is_near_to_expansion(t):
288282
289283 return closest
290284
291- async def distribute_workers (self ):
285+ async def distribute_workers (self , resource_ratio : float = 2 ):
292286 """
293287 Distributes workers across all the bases taken.
288+ Keyword `resource_ratio` takes a float. If the current minerals to gas
289+ ratio is bigger than `resource_ratio`, this function prefer filling geysers
290+ first, if it is lower, it will prefer sending workers to minerals first.
291+ This is only for workers that need to be moved anyways, it will NOT will
292+ geysers on its own.
293+
294+ NOTE: This function is far from optimal, if you really want to have
295+ refined worker control, you should write your own distribution function.
296+ For example long distance mining control and moving workers if a base was killed
297+ are not being handled.
298+
294299 WARNING: This is quite slow when there are lots of workers or multiple bases.
295300 """
296- if not self .state .mineral_field or not self .workers or not self .owned_expansions .ready :
301+ if not self .state .mineral_field or not self .workers or not self .townhalls .ready :
297302 return
298303 actions = []
299304 worker_pool = [worker for worker in self .workers .idle ]
300- bases = self .owned_expansions .ready
305+ bases = self .townhalls .ready
301306 geysers = self .geysers .ready
302307
303308 # list of places that need more workers
@@ -308,53 +313,79 @@ async def distribute_workers(self):
308313 # perfect amount of workers, skip mining place
309314 if not difference :
310315 continue
311- if mining_place .has_vespene :
316+ if mining_place .is_vespene_geyser :
312317 # get all workers that target the gas extraction site
313318 # or are on their way back from it
314319 local_workers = self .workers .filter (
315320 lambda unit : unit .order_target == mining_place .tag
316321 or (unit .is_carrying_vespene and unit .order_target == bases .closest_to (mining_place ).tag )
317322 )
318323 else :
319- # get minerals around expansion
320- local_minerals = self . expansion_locations [ mining_place . position ]. filter (
321- lambda resource : resource . has_minerals
322- )
324+ # get tags of minerals around expansion
325+ local_minerals_tags = {
326+ mineral . tag for mineral in self . state . mineral_field if mineral . distance_to ( mining_place ) <= 8
327+ }
323328 # get all target tags a worker can have
324329 # tags of the minerals he could mine at that base
325330 # get workers that work at that gather site
326331 local_workers = self .workers .filter (
327- lambda unit : unit .order_target in local_minerals . tags
332+ lambda unit : unit .order_target in local_minerals_tags
328333 or (unit .is_carrying_minerals and unit .order_target == mining_place .tag )
329334 )
330335 # too many workers
331336 if difference > 0 :
332- worker_pool .append (local_workers [:difference ])
337+ for worker in local_workers [:difference ]:
338+ worker_pool .append (worker )
333339 # too few workers
334340 # add mining place to deficit bases for every missing worker
335341 else :
336342 deficit_mining_places += [mining_place for _ in range (- difference )]
337343
344+ # prepare all minerals near a base if we have too many workers
345+ # and need to send them to the closest patch
346+ if len (worker_pool ) > len (deficit_mining_places ):
347+ all_minerals_near_base = [
348+ mineral
349+ for mineral in self .state .mineral_field
350+ if any (mineral .distance_to (base ) <= 8 for base in self .townhalls .ready )
351+ ]
338352 # distribute every worker in the pool
339353 for worker in worker_pool :
340354 # as long as have workers and mining places
341355 if deficit_mining_places :
342- # remove current place from the list for next loop
343- current_place = deficit_mining_places .pop (0 )
356+ # choose only mineral fields first if current mineral to gas ratio is less than target ratio
357+ if self .vespene and self .minerals / self .vespene < resource_ratio :
358+ possible_mining_places = [place for place in deficit_mining_places if not place .vespene_contents ]
359+ # else prefer gas
360+ else :
361+ possible_mining_places = [place for place in deficit_mining_places if place .vespene_contents ]
362+ # if preferred type is not available any more, get all other places
363+ if not possible_mining_places :
364+ possible_mining_places = deficit_mining_places
365+ # find closest mining place
366+ current_place = min (deficit_mining_places , key = lambda place : place .distance_to (worker ))
367+ # remove it from the list
368+ deficit_mining_places .remove (current_place )
344369 # if current place is a gas extraction site, go there
345- if current_place .has_vespene :
370+ if current_place .vespene_contents :
346371 actions .append (worker .gather (current_place ))
347372 # if current place is a gas extraction site,
348373 # go to the mineral field that is near and has the most minerals left
349374 else :
350- local_minerals = self . expansion_locations [ current_place . position ]. filter (
351- lambda resource : resource . has_minerals
352- )
375+ local_minerals = [
376+ mineral for mineral in self . state . mineral_field if mineral . distance_to ( current_place ) <= 8
377+ ]
353378 target_mineral = max (local_minerals , key = lambda mineral : mineral .mineral_contents )
354379 actions .append (worker .gather (target_mineral ))
355380 # more workers to distribute than free mining spots
356- # else:
357- # pass
381+ # send to closest if worker is doing nothing
382+ elif worker .is_idle and all_minerals_near_base :
383+ target_mineral = min (all_minerals_near_base , key = lambda mineral : mineral .distance_to (worker ))
384+ actions .append (worker .gather (target_mineral ))
385+ else :
386+ # there are no deficit mining places and worker is not idle
387+ # so dont move him
388+ pass
358389
359390 await self .do_actions (actions )
360391
@@ -366,7 +397,7 @@ def owned_expansions(self) -> Dict[Point2, Unit]:
366397 for el in self .expansion_locations :
367398
368399 def is_near_to_expansion (t ):
369- return t .position . _distance_squared (el ) < self .EXPANSION_GAP_THRESHOLD ** 2
400+ return t .distance_to (el ) < self .EXPANSION_GAP_THRESHOLD
370401
371402 th = next ((x for x in self .townhalls if is_near_to_expansion (x )), None )
372403 if th :
@@ -425,21 +456,21 @@ async def can_cast(
425456 ability_target == 1
426457 or ability_target == Target .PointOrNone .value
427458 and isinstance (target , (Point2 , Point3 ))
428- and unit .position . _distance_squared (target . position ) <= cast_range ** 2
459+ and unit .distance_to (target ) <= cast_range
429460 ): # cant replace 1 with "Target.None.value" because ".None" doesnt seem to be a valid enum name
430461 return True
431462 # Check if able to use ability on a unit
432463 elif (
433464 ability_target in {Target .Unit .value , Target .PointOrUnit .value }
434465 and isinstance (target , Unit )
435- and unit .position . _distance_squared (target . position ) <= cast_range ** 2
466+ and unit .distance_to (target ) <= cast_range
436467 ):
437468 return True
438469 # Check if able to use ability on a position
439470 elif (
440471 ability_target in {Target .Point .value , Target .PointOrUnit .value }
441472 and isinstance (target , (Point2 , Point3 ))
442- and unit .position . _distance_squared (target ) <= cast_range ** 2
473+ and unit .distance_to (target ) <= cast_range
443474 ):
444475 return True
445476 return False
@@ -514,7 +545,7 @@ async def find_placement(
514545 if random_alternative :
515546 return random .choice (possible )
516547 else :
517- return min (possible , key = lambda p : p ._distance_squared (near ))
548+ return min (possible , key = lambda p : p .distance_to_point2 (near ))
518549 return None
519550
520551 def already_pending_upgrade (self , upgrade_type : UpgradeId ) -> Union [int , float ]:
@@ -752,7 +783,7 @@ def _prepare_first_step(self):
752783 """First step extra preparations. Must not be called before _prepare_step."""
753784 if self .townhalls :
754785 self ._game_info .player_start_location = self .townhalls .first .position
755- self ._game_info .map_ramps = self ._game_info ._find_ramps ()
786+ self ._game_info .map_ramps , self . _game_info . vision_blockers = self ._game_info ._find_ramps_and_vision_blockers ()
756787
757788 def _prepare_step (self , state , proto_game_info ):
758789 # Set attributes from new state before on_step."""
0 commit comments