-
Notifications
You must be signed in to change notification settings - Fork 182
Expand file tree
/
Copy pathcyclone_push.py
More file actions
135 lines (115 loc) · 5.92 KB
/
cyclone_push.py
File metadata and controls
135 lines (115 loc) · 5.92 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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from sc2 import maps
from sc2.bot_ai import BotAI
from sc2.data import Difficulty, Race
from sc2.ids.unit_typeid import UnitTypeId
from sc2.main import run_game
from sc2.player import Bot, Computer
from sc2.position import Point2
from sc2.unit import Unit
from sc2.units import Units
class CyclonePush(BotAI):
def select_target(self) -> Point2:
# Pick a random enemy structure's position
targets = self.enemy_structures
if targets:
return targets.random.position
# Pick a random enemy unit's position
targets = self.enemy_units
if targets:
return targets.random.position
# Pick enemy start location if it has no friendly units nearby
if min(unit.distance_to(self.enemy_start_locations[0]) for unit in self.units) > 5:
return self.enemy_start_locations[0]
# Pick a random mineral field on the map
return self.mineral_field.random.position
async def on_step(self, iteration: int):
CCs: Units = self.townhalls(UnitTypeId.COMMANDCENTER)
# If no command center exists, attack-move with all workers and cyclones
if not CCs:
target = self.structures.random_or(self.enemy_start_locations[0]).position
for unit in self.workers | self.units(UnitTypeId.CYCLONE):
unit.attack(target)
return
# Otherwise, grab the first command center from the list of command centers
cc: Unit = CCs.first
# Every 50 iterations (here: every 50*8 = 400 frames)
if iteration % 50 == 0 and self.units(UnitTypeId.CYCLONE).amount > 2:
target: Point2 = self.select_target()
forces: Units = self.units(UnitTypeId.CYCLONE)
# Every 4000 frames: send all forces to attack-move the target position
if iteration % 500 == 0:
for unit in forces:
unit.attack(target)
# Every 400 frames: only send idle forces to attack the target position
else:
for unit in forces.idle:
unit.attack(target)
# While we have less than 22 workers: build more
# Check if we can afford them (by minerals and by supply)
if (
self.can_afford(UnitTypeId.SCV)
and self.supply_workers + self.already_pending(UnitTypeId.SCV) < 22
and cc.is_idle
):
cc.train(UnitTypeId.SCV)
# Build supply depots if we are low on supply, do not construct more than 2 at a time
elif self.supply_left < 3:
if self.can_afford(UnitTypeId.SUPPLYDEPOT) and self.already_pending(UnitTypeId.SUPPLYDEPOT) < 2:
# This picks a near-random worker to build a depot at location
# 'from command center towards game center, distance 8'
await self.build(UnitTypeId.SUPPLYDEPOT, near=cc.position.towards(self.game_info.map_center, 8))
# If we have supply depots (careful, lowered supply depots have a different UnitTypeId: UnitTypeId.SUPPLYDEPOTLOWERED)
if self.structures(UnitTypeId.SUPPLYDEPOT):
# If we have no barracks
if not self.structures(UnitTypeId.BARRACKS):
# If we can afford barracks
if self.can_afford(UnitTypeId.BARRACKS):
# Near same command as above with the depot
await self.build(UnitTypeId.BARRACKS, near=cc.position.towards(self.game_info.map_center, 8))
# If we have a barracks (complete or under construction) and less than 2 gas structures (here: refineries)
if self.structures(UnitTypeId.BARRACKS) and self.gas_buildings.amount < 2:
if self.can_afford(UnitTypeId.REFINERY):
# All the vespene geysirs nearby, including ones with a refinery on top of it
vgs = self.vespene_geyser.closer_than(10, cc)
for vg in vgs:
has_refinery = self.gas_buildings.filter(lambda unit: unit.distance_to(vg) < 1)
if has_refinery:
continue
# Select a worker closest to the vespene geysir
worker: Unit | None = self.select_build_worker(vg)
# Worker can be none in cases where all workers are dead
# or 'select_build_worker' function only selects from workers which carry no minerals
if worker is not None:
# Issue the build command to the worker, important: vg has to be a Unit, not a position
worker.build_gas(vg)
# If we have at least one barracks that is completed, build factory
if self.structures(UnitTypeId.BARRACKS).ready:
if self.structures(UnitTypeId.FACTORY).amount < 3 and not self.already_pending(UnitTypeId.FACTORY):
if self.can_afford(UnitTypeId.FACTORY):
position: Point2 = cc.position.towards_with_random_angle(self.game_info.map_center, 16)
await self.build(UnitTypeId.FACTORY, near=position)
for factory in self.structures(UnitTypeId.FACTORY).ready.idle:
# Reactor allows us to build two at a time
if self.can_afford(UnitTypeId.CYCLONE):
factory.train(UnitTypeId.CYCLONE)
# Saturate gas
for refinery in self.gas_buildings:
if refinery.assigned_harvesters < refinery.ideal_harvesters:
workers: Units = self.workers.closer_than(10, refinery)
if workers:
workers.random.gather(refinery)
for scv in self.workers.idle:
scv.gather(self.mineral_field.closest_to(cc))
def main():
run_game(
maps.get("(2)CatalystLE"),
[
# Human(Race.Terran),
Bot(Race.Terran, CyclonePush()),
Computer(Race.Zerg, Difficulty.Easy),
],
realtime=False,
sc2_version="4.7",
)
if __name__ == "__main__":
main()