Skip to content

Commit

Permalink
Merge pull request #86 from FredNoonienSingh/82-fix-production-bug
Browse files Browse the repository at this point in the history
started implementing production buffer
  • Loading branch information
FredNoonienSingh authored Jan 13, 2025
2 parents 2f237d9 + e67da3a commit 4f5ac4b
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 57 deletions.
49 changes: 29 additions & 20 deletions bot/HarstemsAunt/army_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,48 +145,57 @@ def _runby_request():
if self.bot.structures.filter(lambda struct: struct.type_id in [UnitTypeId.GATEWAY, UnitTypeId.WARPGATE]):
if self.bot.units(UnitTypeId.DARKSHRINE).ready and not opponent_has_detection:
requested_unit: UnitTypeId = UnitTypeId.DARKTEMPLAR
self.requested_units.append(requested_unit)
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)
return
requested_unit: UnitTypeId = UnitTypeId.ZEALOT
self.requested_units.append(requested_unit)
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)
return

def _army_request():
""" request unit for "normal" Army group """
gate_aliases:list = [UnitTypeId.GATEWAY, UnitTypeId.WARPGATE]
if self.bot.structures.filter(lambda struct: struct.type_id in gate_aliases).idle:
if self.bot.structures.filter(lambda struct: struct.type_id in gate_aliases):
stalkers:int = len(self.units(UnitTypeId.STALKER))
zealots:int = len(self.units(UnitTypeId.ZEALOT)) +1

if self.bot.structures(UnitTypeId.TEMPLARARCHIVE) and len(self.units(UnitTypeId.HIGHTEMPLAR)) > 2:
if self.bot.structures(UnitTypeId.TEMPLARARCHIVE) and len(self.units(UnitTypeId.HIGHTEMPLAR)) < 2:
requested_unit: UnitTypeId = UnitTypeId.HIGHTEMPLAR
self.requested_units.append(requested_unit)
return
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)

if not stalkers or stalkers/zealots < 3:
requested_unit: UnitTypeId = UnitTypeId.STALKER
else:
if not zealots < stalkers:
requested_unit: UnitTypeId = UnitTypeId.ZEALOT
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)
else:
requested_unit: UnitTypeId = UnitTypeId.STALKER
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)

if self.bot.structures(UnitTypeId.ROBOTICSFACILITY).idle:
if self.bot.structures(UnitTypeId.ROBOTICSFACILITY):
if not self.has_detection:
requested_unit: UnitTypeId = UnitTypeId.OBSERVER
else:
requested_unit: UnitTypeId = UnitTypeId.IMMORTAL
self.requested_units.append(requested_unit)
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)


if self.bot.structures(UnitTypeId.STARGATE).idle:
if self.bot.structures(UnitTypeId.STARGATE):
if len(self.bot.units.flying) > \
len(self.bot.enemy_units.filter(lambda unit: unit.is_flying and unit.can_attack)):
requested_unit: UnitTypeId = UnitTypeId.PHOENIX
self.requested_units.append(requested_unit)
if not requested_unit in self.requested_units:
self.requested_units.append(requested_unit)

match self.GroupTypeId:
case GroupTypeId.RUN_BY:
logger.info("not Testing Runbys right now")
_runby_request()
case GroupTypeId.ARMY:
_army_request()
#match self.GroupTypeId:
#case GroupTypeId.RUN_BY:
# logger.info("not Testing Runbys right now")
# _runby_request()
#case GroupTypeId.ARMY:
_army_request()

def remove_unit(self, unit_tag:str) -> bool:
""" Removes are unit from ArmyGroup
Expand Down Expand Up @@ -285,7 +294,7 @@ def retreat(self) -> None:
def regroup(self) -> None:
""" regroup command for the group """

if not self.units.filter(lambda unit: unit.distance_to(self.position) > 15):
if not self.units.filter(lambda unit: unit.distance_to(self.position) > 5):
return

self.units.furthest_to(self.position).move(self.position)
Expand Down
7 changes: 4 additions & 3 deletions bot/HarstemsAunt/build_order.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
"""DOCSTRING to shut up the Linter """
from __future__ import annotations

from enum import Enum
from typing import Union, List
from functools import cached_property
Expand All @@ -14,7 +16,6 @@
from .army_group import ArmyGroup
from .common import ALL_STRUCTURES,INITIAL_TECH,DT_TIMING,logger


class InstructionType(Enum):
"""Enumeration containing InstructionTypes """
UNIT_PRODUCTION = 1
Expand All @@ -30,7 +31,7 @@ class BuildInstruction:
""" class representing a Build Instruction """

def __new__(cls,type_id:UnitTypeId,position:Union[Point2,Point3,Unit]=None,\
accuracy:int=0, worker_command:UnitCommand=None):
accuracy:int=0, worker_command:UnitCommand=None) -> BuildInstruction:
""" Creates new instance of BuildInstruction
Args:
Expand Down Expand Up @@ -121,11 +122,11 @@ def instruction_list(self) -> List[BuildInstruction]:
BuildInstruction(UnitTypeId.PYLON,wall_pylon_pos),
BuildInstruction(UnitTypeId.GATEWAY,wall_buildings[0]),
BuildInstruction(UnitTypeId.ASSIMILATOR,vespene_position_0),
BuildInstruction(UnitTypeId.PYLON,tech_pylon_pos),
BuildInstruction(UnitTypeId.ASSIMILATOR,vespene_position_1),
BuildInstruction(UnitTypeId.CYBERNETICSCORE,wall_buildings[1]),
BuildInstruction(UnitTypeId.NEXUS, start_pos),
BuildInstruction(UnitTypeId.GATEWAY, wall_pylon_pos,5),
BuildInstruction(UnitTypeId.PYLON,tech_pylon_pos),
BuildInstruction(UnitTypeId.STALKER, start_pos),
BuildInstruction(UnitTypeId.STALKER, start_pos),
BuildInstruction(UnitTypeId.PYLON, angle_pylon_pos, 1),
Expand Down
3 changes: 2 additions & 1 deletion bot/HarstemsAunt/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@

INITIAL_TECH:Dict = {
Race.Protoss: [UnitTypeId.TWILIGHTCOUNCIL, UnitTypeId.ROBOTICSFACILITY],
Race.Terran: [UnitTypeId.TWILIGHTCOUNCIL, UnitTypeId.TEMPLARARCHIVE],
Race.Terran: [UnitTypeId.TWILIGHTCOUNCIL, UnitTypeId.ROBOTICSFACILITY],
#[UnitTypeId.TWILIGHTCOUNCIL, UnitTypeId.TEMPLARARCHIVE], -> Terran tech change back later
Race.Zerg: [UnitTypeId.TWILIGHTCOUNCIL, UnitTypeId.ROBOTICSFACILITY]
}

Expand Down
55 changes: 29 additions & 26 deletions bot/HarstemsAunt/macro.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,6 @@
from .build_order import BuildOrder, BuildInstruction, InstructionType


async def warp_in_unit(bot: BotAI,unit:UnitTypeId,\
warp_in_position:Union[Point2,Point3,Unit]) -> bool:
pos:Point2= warp_in_position.position.to2.random_on_distance(4)
placement = await bot.find_placement(AbilityId.WARPGATETRAIN_STALKER, pos, placement_step=1)

for gate in bot.structures(UnitTypeId.WARPGATE).idle:
if Utils.can_build_unit(bot, unit):
gate.warp_in(unit, placement)

async def build_gateway_units(bot:BotAI,unit_type:UnitTypeId):
gate_aliases:list = [UnitTypeId.GATEWAY, UnitTypeId.WARPGATE]
if Utils.can_build_unit(bot, unit_type):
for gate in bot.structures.filter(lambda struct: struct.type_id in gate_aliases):
if gate.is_idle and UpgradeId.WARPGATERESEARCH not in bot.researched:
gate.train(unit_type)
else:
warp_in_pos = Utils.get_warp_in_pos(bot)
await warp_in_unit(bot, unit_type, warp_in_pos)

class Macro:
""" Class handling the Marco aspect of the Game """
def __init__(self,bot:BotAI) -> None:
Expand Down Expand Up @@ -134,18 +115,40 @@ async def train_unit(next_step:BuildInstruction):
Args:
next_step (BuildInstruction): next instruction
"""

async def warp_in_unit(bot: BotAI,unit:UnitTypeId,\
warp_in_position:Union[Point2,Point3,Unit]) -> bool:
pos:Point2= warp_in_position.position.to2.random_on_distance(4)
placement = await bot.find_placement(AbilityId.WARPGATETRAIN_STALKER, pos, placement_step=1)

for gate in bot.structures(UnitTypeId.WARPGATE).idle:
if Utils.can_build_unit(bot, unit):
gate.warp_in(unit, placement)

async def build_gateway_units(bot:BotAI,unit_type:UnitTypeId):
gate_aliases:list = [UnitTypeId.GATEWAY, UnitTypeId.WARPGATE]
if Utils.can_build_unit(bot, unit_type):
for gate in bot.structures.filter(lambda struct: struct.type_id in gate_aliases):
if gate.is_idle and UpgradeId.WARPGATERESEARCH not in bot.researched:
gate.train(unit_type)
self.build_order.increment_step()
else:
warp_in_pos = Utils.get_warp_in_pos(bot)
await warp_in_unit(bot, unit_type, warp_in_pos)
self.build_order.increment_step()

#TODO: ADD WARPPRISM LOGIC, SO REINFORCEMENTS CAN BE WARPED IN CLOSE TO FIGHT
unit_type: UnitTypeId = next_step.type_id
production_structure_type = self.get_production_structure(unit_type)
production_structures: Units = self.bot.structures(production_structure_type)
if Utils.can_build_unit(self.bot, next_step.type_id) and production_structures.idle:
if Utils.can_build_unit(self.bot, next_step.type_id) and production_structures:
if not production_structure_type in [UnitTypeId.WARPGATE, UnitTypeId.GATEWAY]:
production_structures[0].train(unit_type)
self.build_order.increment_step()
return
for struct in production_structures:
if struct.is_idle and not self.bot.already_pending(unit_type):
production_structures[0].train(unit_type)
self.build_order.increment_step()
return
await build_gateway_units(self.bot,unit_type)
#TODO: #77 STOP COUNTER FROM GOING CRAZY
self.build_order.increment_step()


next_step: BuildInstruction = self.build_order.next_instruction()
Expand All @@ -154,7 +157,7 @@ async def train_unit(next_step:BuildInstruction):
if not next_step and not self.build_order.buffer:
return

if not next_step.type_id == UnitTypeId.ASSIMILATOR:
if not next_step.type_id == UnitTypeId.ASSIMILATOR and self.build_order.is_performing_initial_build:
structure_cost:Cost = self.bot.calculate_cost(next_step.type_id)
if (self.bot.minerals > (structure_cost.minerals*0.65)):
if not Utils.unittype_in_proximity_to_point(self.bot, UnitTypeId.PROBE, next_step.position):
Expand Down
6 changes: 3 additions & 3 deletions bot/HarstemsAunt/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,13 @@ async def on_unit_created(self, unit:Unit) -> None:
Args:
unit (Unit): Unit that gets created
"""
if self.macro.build_order.is_performing_initial_build \
and not unit.type_id == UnitTypeId.PROBE:
if not unit.type_id == UnitTypeId.PROBE:
self.army_groups[0].units_in_transit.append(unit.tag)

for group in self.army_groups:
if unit.type_id in group.requested_units:
group.units_in_transit.append(unit.tag)
self.macro.build_order.buffer.remove(unit.type_id)
logger.info(f"{unit.type_id} got removed from {group}")

async def on_unit_type_changed(self, unit:Unit, previous_type:UnitTypeId) -> None:
""" Coroutine that gets called when a unit changes type:
Expand Down
8 changes: 4 additions & 4 deletions bot/HarstemsAunt/pathing.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ def __init__(self, bot:BotAI, debug:bool, fade_rate:float = 3.0) -> None:
self.climber_grid: np.ndarray = self.map_data.get_climber_grid()
self.units_grid: np.ndarray = self.map_data.get_pyastar_grid()
self.ground_grid: np.ndarray = self.map_data.get_pyastar_grid()
#self.detection_grid: np.ndarray = self.map_data.get_pyastar_grid()
self.detection_grid: np.ndarray = self.map_data.get_pyastar_grid()
self.air_grid: np.ndarray = self.map_data.get_clean_air_grid()
self.influence_fade_rate: float = fade_rate

def update(self, iteration) -> None:

last_ground_grid:np.ndarray = self.ground_grid
last_air_grid:np.ndarray = self.air_grid
#last_detection_grid: np.ndarray = self.detection_grid
last_detection_grid: np.ndarray = self.detection_grid

last_ground_grid[last_ground_grid != 0] /= self.influence_fade_rate
last_air_grid[last_air_grid != 0] /= self.influence_fade_rate
#last_detection_grid[last_detection_grid != 0] /=self.influence_fade_rate
last_detection_grid[last_detection_grid != 0] /=self.influence_fade_rate

self.ground_grid = self.map_data.get_pyastar_grid() + last_ground_grid
self.air_grid = self.map_data.get_clean_air_grid() + last_air_grid
#self.detection_grid = self.map_data.get_pyastar_grid() + last_detection_grid
self.detection_grid = self.map_data.get_pyastar_grid() + last_detection_grid

for unit in self.bot.all_enemy_units:
if unit.type_id in ALL_STRUCTURES:
Expand Down
68 changes: 68 additions & 0 deletions bot/production_buffer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""DOCSTRING to shut up the Linter """
from __future__ import annotations

from typing import List

from sc2.units import Units
from sc2.bot_ai import BotAI
from sc2.ids.unit_typeid import UnitTypeId


class ProductionRequest:
""" Class Representing the a Production Request """

def __new__(cls, requested_unit:UnitTypeId, army_group_tag:int, build_structure_tag:int) -> ProductionRequest:
""" Creates new instance of Production Request
Args:
requested_unit (UnitTypeId): type of requested Unit
army_group_tag (int): identifier of the ArmyGroup requesting the Unit
Returns:
ProductionRequest: Request for a Unit (gets called in ArmyGroup)
"""
instance = super().__new__(cls)
instance.requested_unit = requested_unit
instance.army_group_tag = army_group_tag
instance.build_structure_tag = build_structure_tag

return instance

def __init__(self, requested_unit:UnitTypeId, army_group_tag:int, build_structure_tag:int) -> None:
self.requested_unit:UnitTypeId = requested_unit
self.army_group_tag:int = army_group_tag
self.build_structure_tag = build_structure_tag

def __repr__(self) -> str:
return f"Unit: {self.requested_unit} requested by {self.army_group_tag}"

@property
def handled(self) -> bool:
return False

@handled.setter
def handled(self, new_status) -> None:
self.handled = new_status

class ProductionBuffer:
""" Buffer for Production Requests before they are full filled"""

def __init__(self,bot:BotAI) -> None:
self.bot:BotAI = bot
self.requests:List[ProductionRequest] = []

@property
def gateways(self) -> Units:
return self.bot.units.filter(lambda struct: struct.type_id \
in [UnitTypeId.WARPGATE, UnitTypeId.GATEWAY])

@property
def stargates(self) -> Units:
return self.bot.units(UnitTypeId.STARGATE)

@property
def robofacilities(self) -> Units:
return self.bot.units(UnitTypeId.ROBOTICSFACILITY)

#def add_request(self) -> None:
# self.

0 comments on commit 4f5ac4b

Please sign in to comment.