forked from BurnySc2/python-sc2
- Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworker_stack_bot.py
More file actions
Latest commit
120 lines (98 loc) · 5.22 KB
/
Copy pathworker_stack_bot.py
File metadata and controls
120 lines (98 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
importsys, os
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
importsc2
fromsc2importRace, Difficulty
fromsc2.ids.unit_typeidimportUnitTypeId
fromsc2.ids.ability_idimportAbilityId
fromsc2.unitimportUnit
fromsc2.unitsimportUnits
fromsc2.positionimportPoint2
fromsc2.playerimportBot, Computer
fromsc2.dataimportrace_townhalls
fromloguruimportlogger
fromtypingimportDict, List, Set
"""
This bot attempts to stack workers 'perfectly'.
This is only a demo that works on game start, but does not work when adding more workers or bases.
This bot exists only to showcase how to keep track of mineral tag over multiple steps / frames.
Task for the user who wants to enhance this bot:
- Allow mining from vespene geysirs
- Remove dead workers and re-assign (new) workers to that mineral patch, or pick a worker from a long distance mineral patch
- Re-assign workers when new base is completed (or near complete)
- Re-assign workers when base died
- Re-assign workers when mineral patch mines out
- Re-assign workers when gas mines out
"""
classWorkerStackBot(sc2.BotAI):
def__init__(self):
self.worker_to_mineral_patch_dict: Dict[int, int] = {}
self.mineral_patch_to_list_of_workers: Dict[int, Set[int]] = {}
self.minerals_sorted_by_distance: Units=Units([], self)
# Distance 0.01 to 0.1 seems fine
self.townhall_distance_threshold=0.01
# Distance factor between 0.95 and 1.0 seems fine
self.townhall_distance_factor=1
asyncdefon_start(self):
self.client.game_step=1
awaitself.assign_workers()
asyncdefassign_workers(self):
self.minerals_sorted_by_distance=self.mineral_field.closer_than(
10, self.start_location
).sorted_by_distance_to(self.start_location)
# Assign workers to mineral patch, start with the mineral patch closest to base
formineralinself.minerals_sorted_by_distance:
# Assign workers closest to the mineral patch
workers=self.workers.tags_not_in(self.worker_to_mineral_patch_dict).sorted_by_distance_to(mineral)
forworkerinworkers:
# Assign at most 2 workers per patch
# This dict is not really used further down the code, but useful to keep track of how many workers are assigned to this mineral patch - important for when the mineral patch mines out or a worker dies
iflen(self.mineral_patch_to_list_of_workers.get(mineral.tag, [])) <2:
iflen(self.mineral_patch_to_list_of_workers.get(mineral.tag, [])) ==0:
self.mineral_patch_to_list_of_workers[mineral.tag] = {worker.tag}
else:
self.mineral_patch_to_list_of_workers[mineral.tag].add(worker.tag)
# Keep track of which mineral patch the worker is assigned to - if the mineral patch mines out, reassign the worker to another patch
self.worker_to_mineral_patch_dict[worker.tag] =mineral.tag
else:
break
asyncdefon_step(self, iteration: int):
ifself.worker_to_mineral_patch_dict:
# Quick-access cache mineral tag to mineral Unit
minerals: Dict[int, Unit] = {mineral.tag: mineralformineralinself.mineral_field}
forworkerinself.workers:
ifnotself.townhalls:
logger.error(f"All townhalls died - can't return resources")
break
worker: Unit
mineral_tag=self.worker_to_mineral_patch_dict[worker.tag]
mineral=minerals.get(mineral_tag, None)
ifmineralisNone:
logger.error(f"Mined out mineral with tag {mineral_tag} for worker {worker.tag}")
continue
# Order worker to mine at target mineral patch if isn't carrying minerals
ifnotworker.is_carrying_minerals:
ifnotworker.is_gatheringorworker.order_target!=mineral.tag:
worker.gather(mineral)
# Order worker to return minerals if carrying minerals
else:
th=self.townhalls.closest_to(worker)
# Move worker in front of the nexus to avoid deceleration until the last moment
ifworker.distance_to(th) >th.radius+worker.radius+self.townhall_distance_threshold:
pos: Point2=th.position
worker.move(pos.towards(worker, th.radius*self.townhall_distance_factor))
worker.return_resource(queue=True)
else:
worker.return_resource()
worker.gather(mineral, queue=True)
# Print info every 30 game-seconds
ifself.state.game_loop% (22.4*30) ==0:
logger.info(f"{self.time_formatted} Mined a total of {int(self.state.score.collected_minerals)} minerals")
defmain():
sc2.run_game(
sc2.maps.get("AcropolisLE"),
[Bot(Race.Protoss, WorkerStackBot()), Computer(Race.Terran, Difficulty.Medium)],
realtime=False,
random_seed=0,
)
if__name__=="__main__":
main()