Advertisement
Guest User

Untitled

a guest
Jan 24th, 2020
127
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.73 KB | None | 0 0
  1. import sc2
  2. import random
  3.  
  4. from sc2 import Race, Difficulty, position
  5. from sc2.position import Point2, Point3
  6. from sc2.unit import Unit
  7. from sc2.player import Bot, Computer
  8. from sc2.player import Human
  9. from sc2.ids.unit_typeid import UnitTypeId
  10. from sc2.ids.ability_id import AbilityId
  11. from sc2.ids.upgrade_id import UpgradeId
  12. from sc2.units import Units
  13.  
  14.  
  15. class AlphaWannabe(sc2.BotAI):
  16. newNexusPositions = [];
  17.  
  18. async def distribute_workers(self, performanceHeavy=True, onlySaturateGas=False,):
  19. mineralTags = [x.tag for x in self.mineral_field]
  20. gas_buildingTags = [x.tag for x in self.gas_buildings]
  21.  
  22. workerPool = Units([], self)
  23. workerPoolTags = set()
  24.  
  25. # Find all gas_buildings that have surplus or deficit
  26. deficit_gas_buildings = {}
  27. surplusgas_buildings = {}
  28. for g in self.gas_buildings.filter(lambda x: x.vespene_contents > 0):
  29. # Only loop over gas_buildings that have still gas in them
  30. deficit = g.ideal_harvesters - g.assigned_harvesters
  31. if deficit > 0:
  32. deficit_gas_buildings[g.tag] = {"unit": g, "deficit": deficit}
  33. elif deficit < 0:
  34. surplusWorkers = self.workers.closer_than(10, g).filter(
  35. lambda w: w not in workerPoolTags
  36. and len(w.orders) == 1
  37. and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
  38. and w.orders[0].target in gas_buildingTags
  39. )
  40. for i in range(-deficit):
  41. if surplusWorkers.amount > 0:
  42. w = surplusWorkers.pop()
  43. workerPool.append(w)
  44. workerPoolTags.add(w.tag)
  45. surplusgas_buildings[g.tag] = {"unit": g, "deficit": deficit}
  46.  
  47. # Find all townhalls that have surplus or deficit
  48. deficitTownhalls = {}
  49. surplusTownhalls = {}
  50. if not onlySaturateGas:
  51. for th in self.townhalls:
  52. deficit = th.ideal_harvesters - th.assigned_harvesters
  53. if deficit > 0:
  54. deficitTownhalls[th.tag] = {"unit": th, "deficit": deficit}
  55. elif deficit < 0:
  56. surplusWorkers = self.workers.closer_than(10, th).filter(
  57. lambda w: w.tag not in workerPoolTags
  58. and len(w.orders) == 1
  59. and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
  60. and w.orders[0].target in mineralTags
  61. )
  62. # workerPool.extend(surplusWorkers)
  63. for i in range(-deficit):
  64. if surplusWorkers.amount > 0:
  65. w = surplusWorkers.pop()
  66. workerPool.append(w)
  67. workerPoolTags.add(w.tag)
  68. surplusTownhalls[th.tag] = {"unit": th, "deficit": deficit}
  69.  
  70. if all(
  71. [
  72. len(deficit_gas_buildings) == 0,
  73. len(surplusgas_buildings) == 0,
  74. len(surplusTownhalls) == 0 or deficitTownhalls == 0,
  75. ]
  76. ):
  77. # Cancel early if there is nothing to balance
  78. return
  79.  
  80. # Check if deficit in gas less or equal than what we have in surplus, else grab some more workers from surplus bases
  81. deficitGasCount = sum(
  82. gasInfo["deficit"] for gasTag, gasInfo in deficit_gas_buildings.items() if gasInfo["deficit"] > 0
  83. )
  84. surplusCount = sum(
  85. -gasInfo["deficit"] for gasTag, gasInfo in surplusgas_buildings.items() if gasInfo["deficit"] < 0
  86. )
  87. surplusCount += sum(-thInfo["deficit"] for thTag, thInfo in surplusTownhalls.items() if thInfo["deficit"] < 0)
  88.  
  89. if deficitGasCount - surplusCount > 0:
  90. # Grab workers near the gas who are mining minerals
  91. for gTag, gInfo in deficit_gas_buildings.items():
  92. if workerPool.amount >= deficitGasCount:
  93. break
  94. workersNearGas = self.workers.closer_than(10, gInfo["unit"]).filter(
  95. lambda w: w.tag not in workerPoolTags
  96. and len(w.orders) == 1
  97. and w.orders[0].ability.id in [AbilityId.HARVEST_GATHER]
  98. and w.orders[0].target in mineralTags
  99. )
  100. while workersNearGas.amount > 0 and workerPool.amount < deficitGasCount:
  101. w = workersNearGas.pop()
  102. workerPool.append(w)
  103. workerPoolTags.add(w.tag)
  104.  
  105. # Now we should have enough workers in the pool to saturate all gases, and if there are workers left over, make them mine at townhalls that have mineral workers deficit
  106. for gTag, gInfo in deficit_gas_buildings.items():
  107. if performanceHeavy:
  108. # Sort furthest away to closest (as the pop() function will take the last element)
  109. workerPool.sort(key=lambda x: x.distance_to(gInfo["unit"]), reverse=True)
  110. for i in range(gInfo["deficit"]):
  111. if workerPool.amount > 0:
  112. w = workerPool.pop()
  113. if len(w.orders) == 1 and w.orders[0].ability.id in [AbilityId.HARVEST_RETURN]:
  114. self.do(w.gather(gInfo["unit"], queue=True))
  115. else:
  116. self.do(w.gather(gInfo["unit"]))
  117.  
  118. if not onlySaturateGas:
  119. # If we now have left over workers, make them mine at bases with deficit in mineral workers
  120. for thTag, thInfo in deficitTownhalls.items():
  121. if performanceHeavy:
  122. # Sort furthest away to closest (as the pop() function will take the last element)
  123. workerPool.sort(key=lambda x: x.distance_to(thInfo["unit"]), reverse=True)
  124. for i in range(thInfo["deficit"]):
  125. if workerPool.amount > 0:
  126. w = workerPool.pop()
  127. mf = self.mineral_field.closer_than(10, thInfo["unit"]).closest_to(w)
  128. if len(w.orders) == 1 and w.orders[0].ability.id in [AbilityId.HARVEST_RETURN]:
  129. self.do(w.gather(mf, queue=True))
  130. else:
  131. self.do(w.gather(mf))
  132.  
  133. async def microUnits(self):
  134. enemies = self.enemy_units | self.enemy_structures
  135. enemies_can_attack = enemies.filter(lambda unit: unit.can_attack_ground)
  136. for r in self.units(UnitTypeId.MARINE):
  137.  
  138. # Move to range 15 of closest unit if reaper is below 20 hp and not regenerating
  139. enemyThreatsClose = enemies_can_attack.filter(
  140. lambda unit: unit.distance_to(r) < 15
  141. ) # Threats that can attack the reaper
  142.  
  143. if r.health_percentage < 2 / 5 and enemyThreatsClose:
  144. retreatPoints = self.neighbors8(r.position, distance=2) | self.neighbors8(r.position, distance=4)
  145. # Filter points that are pathable
  146. retreatPoints = {x for x in retreatPoints if self.in_pathing_grid(x)}
  147. if retreatPoints:
  148. closestEnemy = enemyThreatsClose.closest_to(r)
  149. retreatPoint = closestEnemy.position.furthest(retreatPoints)
  150. self.do(r.move(retreatPoint))
  151. continue # Continue for loop, dont execute any of the following
  152.  
  153. # Reaper is ready to attack, shoot nearest ground unit
  154. enemyGroundUnits = enemies.filter(
  155. lambda unit: unit.distance_to(r) < 5 and not unit.is_flying
  156. ) # Hardcoded attackrange of 5
  157. if r.weapon_cooldown == 0 and enemyGroundUnits:
  158. enemyGroundUnits = enemyGroundUnits.sorted(lambda x: x.distance_to(r))
  159. closestEnemy = enemyGroundUnits[0]
  160. self.do(r.attack(closestEnemy))
  161. continue # Continue for loop, dont execute any of the following
  162.  
  163. # Move to max unit range if enemy is closer than 4
  164. enemyThreatsVeryClose = enemies.filter(
  165. lambda unit: unit.can_attack_ground and unit.distance_to(r) < 4.5
  166. ) # Hardcoded attackrange minus 0.5
  167. # Threats that can attack the reaper
  168. if r.weapon_cooldown != 0 and enemyThreatsVeryClose:
  169. retreatPoints = self.neighbors8(r.position, distance=2) | self.neighbors8(r.position, distance=4)
  170. # Filter points that are pathable by a reaper
  171. retreatPoints = {x for x in retreatPoints if self.in_pathing_grid(x)}
  172. if retreatPoints:
  173. closestEnemy = enemyThreatsVeryClose.closest_to(r)
  174. retreatPoint = max(retreatPoints, key=lambda x: x.distance_to(closestEnemy) - x.distance_to(r))
  175. self.do(r.move(retreatPoint))
  176. continue # Continue for loop, don't execute any of the following
  177.  
  178. # # Move to nearest enemy ground unit/building because no enemy unit is closer than 5
  179. # allEnemyGroundUnits = self.enemy_units.not_flying
  180. # if allEnemyGroundUnits:
  181. # closestEnemy = allEnemyGroundUnits.closest_to(r)
  182. # self.do(r.move(closestEnemy))
  183. # continue # Continue for loop, don't execute any of the following
  184.  
  185. # Move to random enemy start location if no enemy buildings have been seen
  186. self.do(r.move(random.choice(self.enemy_start_locations)))
  187.  
  188. async def buildExpansion(self, buildingId): # UnitTypeId.whatever
  189. if (self.can_afford(UnitTypeId.NEXUS)):
  190. workers = self.workers.gathering
  191. if (workers):
  192. worker = workers.furthest_to(workers.center)
  193. # If a placement location was found
  194. location = await self.get_next_expansion()
  195. if location:
  196. self.do(worker.build(UnitTypeId.NEXUS, location), subtract_cost=True)
  197. self.newNexusPositions.append(location)
  198.  
  199. async def trainUnits(self, buildingName, unitName):
  200. for sg in self.structures(buildingName).ready.idle:
  201. if self.can_afford(unitName):
  202. self.do(sg.train(unitName), subtract_cost=True, subtract_supply=True)
  203.  
  204.  
  205. async def warpUnits(self, abilityId, unitTypeId):
  206. for warpgate in self.structures(UnitTypeId.WARPGATE).ready:
  207. abilities = await self.get_available_abilities(warpgate)
  208. if abilityId in abilities:
  209. closestPylon = self.structures(UnitTypeId.PYLON).ready.closest_to(self.enemy_start_locations[0])
  210. placement = await self.find_placement(abilityId, closestPylon.position, placement_step=1)
  211. if placement is None:
  212. print("can't find valid place to warp in a unit!")
  213. return
  214. self.do(warpgate.warp_in(unitTypeId, placement), subtract_cost=True, subtract_supply=True)
  215.  
  216. async def trainWorker(self, buildingId, workerId): #UnitTypeId.whatever, UnitTypeId.whatever
  217. if (
  218. self.can_afford(workerId)
  219. and self.supply_left > 0
  220. and
  221. (
  222. self.townhalls(buildingId).idle
  223. or self.townhalls(buildingId).idle
  224. )
  225. ):
  226. for th in self.townhalls.idle:
  227. self.do(th.train(workerId), subtract_cost=True, subtract_supply=True)
  228.  
  229.  
  230.  
  231.  
  232.  
  233.  
  234.  
  235. async def manageIdleWorkers(self):
  236. if self.townhalls:
  237. for w in self.workers.idle:
  238. th = self.townhalls.closest_to(w)
  239. mfs = self.mineral_field.closer_than(10, th)
  240. if mfs:
  241. mf = mfs.closest_to(w)
  242. self.do(w.gather(mf))
  243.  
  244.  
  245. async def buildStructure(self, structureId): # UnitTypeId
  246. if (self.can_afford(structureId)):
  247. workers = self.workers.gathering
  248. if (workers):
  249. worker = workers.furthest_to(workers.center)
  250. # If a placement location was found
  251. location = await self.find_placement(structureId, worker.position, placement_step=3)
  252. if location:
  253. # Order worker to build exactly on that location
  254. self.do(worker.build(structureId, location), subtract_cost=True)
  255.  
  256. async def buildGeyser(self, geyserId):
  257. for nexus in self.townhalls.ready:
  258. vgs = self.vespene_geyser.closer_than(15, nexus)
  259. for vg in vgs:
  260. if (self.can_afford(geyserId)):
  261.  
  262. worker = self.select_build_worker(vg.position)
  263. if worker is None:
  264. break
  265.  
  266. if not self.gas_buildings or not self.gas_buildings.closer_than(1, vg):
  267. self.do(worker.build(geyserId, vg), subtract_cost=True)
  268. self.do(worker.stop(queue=True))
  269.  
  270. async def upgradeWarpgate(self):
  271. if (self.can_afford(AbilityId.RESEARCH_WARPGATE)):
  272. cCore = self.structures(UnitTypeId.CYBERNETICSCORE).ready.first
  273. self.do(cCore(AbilityId.RESEARCH_WARPGATE), subtract_cost=True)
  274.  
  275. async def upgradeBlink(self):
  276. if (self.can_afford(UpgradeId.BLINKTECH)):
  277. tCouncil = self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready.first
  278. self.do(tCouncil(AbilityId.RESEARCH_BLINK), subtract_cost=True)
  279.  
  280. async def morphGateways(self):
  281. for gateway in self.structures(UnitTypeId.GATEWAY).ready.idle:
  282. if self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 1:
  283. self.do(gateway(AbilityId.RESEARCH_WARPGATE))
  284.  
  285. async def buildPylon(self):
  286. if self.can_afford(UnitTypeId.PYLON):
  287. if (self.newNexusPositions != []):
  288. wantedBuildLocation = self.newNexusPositions[0] #.position ?
  289. self.newNexusPositions.pop(0)
  290. else:
  291. wantedBuildLocation = self.structures(UnitTypeId.NEXUS).ready.random.position # can also use self.townhalls)
  292.  
  293.  
  294. pylonPosition = wantedBuildLocation.towards_with_random_angle(self.game_info.map_center, random.randrange(5, 15), 3.1415/3)
  295. await self.build(UnitTypeId.PYLON, near=pylonPosition)
  296.  
  297. async def attackOpponent(self):
  298. for stalker in self.units(UnitTypeId.STALKER):
  299. #if I don't have many stalkers, don't do anything
  300. targets = (self.enemy_units | self.enemy_structures).filter(lambda unit: unit.can_be_attacked)
  301. if targets:
  302. target = targets.closest_to(stalker)
  303. self.do(stalker.attack(target))
  304. else:
  305. self.do(stalker.attack(self.enemy_start_locations[0]))
  306. if (stalker.shield_percentage < 0.5):
  307. await self.blinkAwayFromEnemy(stalker)
  308.  
  309. async def sendUnitsToDefend(self):
  310. #locationToAttack = self.structures(UnitTypeId.NEXUS).closest_to(self.enemy_start_locations[0]).position
  311. for stalker in self.units(UnitTypeId.STALKER).ready:
  312. targets = (self.enemy_units).filter(lambda unit: unit.can_be_attacked)
  313. #targets = (self.enemy_units | self.enemy_structures).filter(lambda unit: unit.can_be_attacked)
  314. if (stalker.shield_percentage < 0.5):
  315. await self.blinkAwayFromEnemy(stalker)
  316.  
  317. if targets:
  318. target = targets.closest_to(stalker)
  319. self.do(stalker.attack(target))
  320. else:
  321. self.do(stalker.attack(self.structures(UnitTypeId.NEXUS).closest_to(self.enemy_start_locations[0]).position))
  322.  
  323. async def blinkAwayFromEnemy(self, stalker):
  324. blinkAbility = await self.get_available_abilities(stalker)
  325. enemies = self.enemy_units
  326. if (enemies == None or enemies == []):
  327. return
  328. closestEnemyPosition = enemies.closest_to(stalker).position
  329. hopefullySafePosition = (-closestEnemyPosition[0], -closestEnemyPosition[1])
  330. self.do(stalker(AbilityId.EFFECT_BLINK, position.Point2(hopefullySafePosition))) #TODO: fix this!
  331.  
  332. # async def blinkOnOpposingArmy(self, stalker): # teleports single stalkers, not the entire army.
  333. # blinkAbility = await self.get_available_abilities(stalker)
  334. # enemies = self.enemy_units
  335. # if (enemies == None or enemies == []):
  336. # return
  337. # closestEnemyPosition = enemies.closest_to(stalker).position
  338. # hopefullySafePosition = (-closestEnemyPosition[0], -closestEnemyPosition[1])
  339. # print(closestEnemyPosition)
  340. # print(hopefullySafePosition)
  341. # self.do(stalker(AbilityId.EFFECT_BLINK, enemies.closest_to(stalker).position * 8))
  342.  
  343.  
  344. # locationToAttack = self.structures(UnitTypeId.NEXUS).random.position
  345. # for stalker in self.units(UnitTypeId.STALKER):
  346. # self.do(stalker.attack(locationToAttack))
  347.  
  348.  
  349.  
  350.  
  351.  
  352. async def on_step(self, iteration: int):
  353. if iteration >= 0:
  354.  
  355. ### things that should ALWAYS run - micro, mainly
  356. #Always do stuff with idle workers/new workers/whatever
  357. await self.manageIdleWorkers()
  358. if (iteration % 25 == 0):
  359. await self.distribute_workers()
  360.  
  361. if (self.already_pending_upgrade(UpgradeId.BLINKTECH) == 1 and self.units(UnitTypeId.STALKER).amount >= 20):
  362. await self.attackOpponent()
  363. else:
  364. await self.sendUnitsToDefend()
  365.  
  366. #await self.morphGateways()
  367.  
  368.  
  369. ### things I want to run ASAP - HUGE priority
  370.  
  371. # Build a cybernetics core
  372. if (self.structures(UnitTypeId.GATEWAY).ready and not self.structures(UnitTypeId.CYBERNETICSCORE) and self.already_pending(UnitTypeId.CYBERNETICSCORE) == 0):
  373. await self.buildStructure(UnitTypeId.CYBERNETICSCORE)
  374. return
  375.  
  376. # Build a twilight council
  377. if (self.structures(UnitTypeId.CYBERNETICSCORE).ready and not self.structures(UnitTypeId.TWILIGHTCOUNCIL) and self.already_pending(UnitTypeId.TWILIGHTCOUNCIL) == 0):
  378. await self.buildStructure(UnitTypeId.TWILIGHTCOUNCIL)
  379. return
  380.  
  381.  
  382. # Research warpgate asap
  383. if (self.structures(UnitTypeId.CYBERNETICSCORE).ready and self.already_pending_upgrade(UpgradeId.WARPGATERESEARCH) == 0):
  384. await self.upgradeWarpgate() #Research WarpGate ASAP
  385. return
  386.  
  387. # Upgrade blink asap
  388. if (self.structures(UnitTypeId.TWILIGHTCOUNCIL).ready and self.already_pending_upgrade(UpgradeId.BLINKTECH) == 0):
  389. await self.upgradeBlink() # Research blinktech ASAP
  390. return
  391.  
  392. ### "other" - run whenever if it should/can.
  393. # Supply stuff starts - deal with supply limits/cap/whatever.
  394. if (self.supply_cap <= 50):
  395. if (self.supply_left <= 10 and self.already_pending(UnitTypeId.PYLON) < 1):
  396. await self.buildPylon()
  397.  
  398. if (self.supply_cap <= 100 and self.supply_cap > 50):
  399. if (self.supply_left <= 20 & self.already_pending(UnitTypeId.PYLON) < 2):
  400. await self.buildPylon()
  401.  
  402. if (self.supply_cap <= 199 and self.supply_cap > 100):
  403. if (self.supply_left <= 25 & (self.already_pending(UnitTypeId.PYLON) < 3) & (self.supply_cap != 200)):
  404. await self.buildPylon()
  405. #Supply stuff ends
  406.  
  407.  
  408.  
  409. #Build 2 gateways per base expand I have.
  410. if (self.townhalls.amount * 2 > self.structures(UnitTypeId.GATEWAY).amount + self.structures(UnitTypeId.WARPGATE).amount):
  411. await self.buildStructure(UnitTypeId.GATEWAY) # Only build 2 gateways per base
  412.  
  413. #Train probes, 19 per base (1 refinery per base + 16 mineral patches = 19 probes per expo)
  414. if (self.townhalls.amount * 19 > self.supply_workers):
  415. await self.trainWorker(UnitTypeId.NEXUS, UnitTypeId.PROBE)
  416.  
  417. # Build 1 assimilator per base - not necessarily at that base.
  418. if (self.townhalls.amount > self.structures(UnitTypeId.ASSIMILATOR).amount):
  419. await self.buildGeyser(UnitTypeId.ASSIMILATOR)
  420.  
  421. # Build/warp in stalkers whenever possible.
  422. await self.trainUnits(UnitTypeId.GATEWAY, UnitTypeId.STALKER)
  423. await self.warpUnits(AbilityId.WARPGATETRAIN_STALKER, UnitTypeId.STALKER)
  424.  
  425. # Build 1 nexus at a time. #TODO: Only build 1 nexus at once. (never double/triple expand at the same time)
  426. if (self.structures(UnitTypeId.NEXUS).amount <= 3): # & self.already_pending(UnitTypeId.NEXUS) < 1
  427. await self.buildExpansion(UnitTypeId.NEXUS) # Build up to 4 bases, whenever I have enough minerals banked for them.
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.  
  435.  
  436.  
  437.  
  438.  
  439.  
  440.  
  441.  
  442.  
  443.  
  444.  
  445.  
  446.  
  447.  
  448.  
  449.  
  450.  
  451. sc2.run_game(sc2.maps.get("AcropolisLE"), [
  452. Bot(Race.Protoss, AlphaWannabe()),
  453. Computer(Race.Protoss, Difficulty.Hard)
  454. ], realtime=False)
  455.  
  456. # force an earlier expo - like 1 or 2 minutes in
  457. # Chrono boost
  458. # I always save 150 mineralss at the start?
  459. # efficiently micro probes at the very start of the game
  460. # maximize pylon coverage ?
  461. # wall off?
  462. # invisibilility detection? (DTs)
  463. # build DTs
  464. # Zealot runbies? STALKER RUNBIES?
  465. # warp prism micro too?
  466. # WARP PRISM DROP ON THEIR THIRD? owo
  467. # teleport in opponent's army if I have more supply of an army? (beat siege tanks faster)
  468. # don't trap my own units in my base
  469. # scout for proxies/any secrets
  470. # if I've basically won for sure, look around hte map and kill ANYTHING else
  471. # build Zealots early game
  472. # scout, if opponent is going to all-in early prepare for it
  473. # bm opponent with nice emotes
  474. # limit number of expansions
  475. # one expo at once, only if fully saturated gas + minerals ( bases & 19)
  476. # blink towards opponents base / one for tank vision, then on top of tanks
  477. # allow saving-up for stuff (if whatever, return - don't run any other code that spends minerals!
  478. # only build an expo if one isn't already building
  479.  
  480. # for building pylons, at 84 supply, switch it to be the other way - building more pylons causes mass pylon production lmao.
  481. # besides that, add more to the sendUnitsToDefend function - if I need to teleport away, teleport away from the enemies.
  482. # teleport away from the CLOSEST ENEMY!
  483. # implement stutter-stepping with stalkers
  484. # if I'm losing the fight, retreat
  485.  
  486. # Expand/whatever, if I have less than perfect mineral saturation (e.g if a mineral field dissapears)
  487. # Build the assimilator at the new base - not the first/second/third base, etc. --> add that to the newNexusPositions array ? check if enough minerals for both, then do?
  488. # SendUnitsToDefend --> only defend if it's within X distance of a friendly building/nexus/whatever.
  489. # Mass recall!
  490. # chase enemies function
  491. # don't warp in at damaged pylon (probably dying)
  492. # blink into opposing army
  493. # fix "can't find warp-in location"
  494. # get surrounds / concave on the opponent
  495. # fix teleporting to use my position - enemy position or however that works.exe
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement